Advertisement
  1. Code
  2. PHP

Menggabungkan Laravel 4 dan Backbone

Scroll to top
Read Time: 71 min

() translation by (you can also view the original English article)

Untuk tutorial ini, kita akan membangun satu halaman aplikasi menggunakan Laravel 4 dan Backbone.js. Kerangka kedua membuatnya sangat mudah untuk menggunakan mesin template yang berbeda selain default mereka masing-masing, jadi kita akan menggunakan kumis, yang merupakan mesin yang umum untuk kedua. Dengan menggunakan bahasa template yang sama pada kedua sisi dari aplikasi kami, kami akan dapat berbagi kami betweem pemandangan mereka, menyelamatkan kita dari keharusan untuk mengulangi pekerjaan kami beberapa kali.

Aplikasi tulang punggung kami akan didukung oleh Laravel 4 JSON API yang kita akan mengembangkan bersama-sama. Laravel 4 dilengkapi dengan beberapa fitur baru yang membuat pengembangan API ini sangat mudah. Saya akan menunjukkan Anda beberapa trik sepanjang jalan untuk memungkinkan Anda untuk tetap sedikit lebih terorganisir.

Semua dependensi kita akan dikelola oleh manajer paket, tidak akan ada manual men-download atau memperbarui perpustakaan untuk aplikasi ini! Selain itu, aku akan menunjukkan kepada Anda bagaimana memanfaatkan kekuatan tambahan kecil dari beberapa dependensi kita.

Untuk proyek ini kita akan menggunakan:

  • Laravel 4: Besar PHP framework.
  • Mustache.php: PHP mesin render untuk kumis.
  • Mustache.js: JavaScript mesin render untuk kumis.
  • Jeffrey cara Generator untuk Laravel 4: kita dapat meningkatkan alur kerja kami dengan menghasilkan beberapa kode boilerplate bagi kita menggunakan generator ini.
  • Twitter Bootstrap: Sebuah front-end perpustakaan untuk membantu dalam styling kami.
  • PHPUnit: PHP pengujian suite.
  • Ejekan: Digunakan untuk mengejek obyek PHP saat pengujian.
  • Backbone.js: MVC Javascript untuk aplikasi satu halaman kami.
  • Underscore.js: Ketergantungan tulang punggung, dan sebuah toolkit kecil besar fungsi.

Untuk menyelesaikan tutorial ini, Anda akan memerlukan item berikut diinstal:

  • Komposer: Anda dapat men-download ini dari situs, saya merekomendasikan petunjuk instalasi global terletak di sini.
  • Node + NPM: installer pada homepage akan menginstal kedua item.
  • Compiler kurang: Jika Anda pada Mac, saya merekomendasikan CodeKit. Namun, terlepas dari sistem operasi Anda, atau jika Anda tidak merasa seperti membayar untuk CodeKit, Anda dapat menginstal kompilator kurang untuk Node.js dengan mengetik npm menginstal -g kurang pada prompt perintah.

Bagian 1: Arsitektur dasar

Hal pertama yang pertama, kita perlu untuk mendapatkan setup aplikasi kita sebelum kita dapat mulai menambahkan logika bisnis kami. Kami akan melakukan setup dasar 4 Laravel dan mendapatkan semua dependensi kita diinstal menggunakan Manajer paket kami.

Git

Mari kita mulai dengan menciptakan sebuah repositori git untuk bekerja di. Untuk referensi, repo seluruh ini akan dibuat tersedia untuk umum di https://github.com/conarwelsh/nettuts-laravel4-and-backbone.

1
mkdir project && cd project
2
git init

Laravel 4 instalasi

Laravel 4 menggunakan komposer untuk menginstal semua dependensi, tapi pertama kita akan membutuhkan struktur aplikasi untuk menginstal ke dalam. Cabang "mengembangkan" Laravel's Github repositori adalah rumah bagi struktur aplikasi ini. Namun, pada saat menulis artikel ini, Laravel 4 adalah masih dalam versi beta, jadi saya perlu dipersiapkan untuk struktur ini untuk mengubah setiap saat. Dengan menambahkan Laravel sebagai repositori berjarak, kita dapat menarik perubahan ini setiap kali kita perlu. Bahkan, sementara sesuatu dalam modus beta, itu adalah praktik yang baik untuk menjalankan perintah tersebut setelah setiap update komposer. Namun, Laravel 4 sekarang adalah versi terbaru, stabil.

1
git remote add laravel https://github.com/laravel/laravel
2
git fetch laravel
3
git merge laravel/develop
4
git add . && git commit -am "commit the laravel application structure"

Jadi kita memiliki struktur aplikasi, tetapi semua file perpustakaan yang Laravel tidak belum diinstal. Anda akan melihat akar dari aplikasi kita ada file bernama composer.json. Ini adalah file yang akan terus melacak jumlah semua dependensi yang memerlukan aplikasi Laravel kami. Sebelum kami memberitahu komposer men-download dan menginstal mereka, mari kita pertama kali menambahkan beberapa dependensi yang lebih banyak bahwa kita akan membutuhkan. Kami akan menambahkan:

  • Jeffrey cara Generator: beberapa perintah yang sangat berguna untuk lebih meningkatkan alur kerja kami dengan secara otomatis menghasilkan file tersembunyi: bagi kita.
  • Kumis Laravel 4: Ini akan memungkinkan kita untuk mulus menggunakan Mustache.php di proyek Laravel kami, sama seperti kita Blade.
  • Twitter Bootstrap: Kami akan menggunakan file kurang dari proyek ini untuk mempercepat pembangunan front-end kami.
  • PHPUnit: Kami akan melakukan beberapa TDD untuk API JSON kami, PHPUnit akan mesin pengujian kami.
  • Ejekan: Ejekan akan membantu kita "mengejek" objek selama pengujian kami.

PHPUnit dan olok-olok hanya diperlukan dalam lingkungan pengembangan kami, jadi kita akan menentukan bahwa dalam file composer.json kami.


Composer.JSON

1
{
2
  "require": {
3
    "laravel/framework": "4.0.*",
4
    "way/generators": "dev-master",
5
    "twitter/bootstrap": "dev-master",
6
    "conarwelsh/mustache-l4": "dev-master"
7
  },
8
  "require-dev": {
9
    "phpunit/phpunit": "3.7.*",
10
    "mockery/mockery": "0.7.*"
11
  },
12
  "autoload": {
13
    "classmap": [
14
      "app/commands",
15
      "app/controllers",
16
      "app/models",
17
      "app/database/migrations",
18
      "app/database/seeds",
19
      "app/tests/TestCase.php"
20
    ]
21
  },
22
  "scripts": {
23
    "post-update-cmd": "php artisan optimize"
24
  },
25
  "minimum-stability": "dev"
26
}

Sekarang kita hanya perlu memberitahu komposer untuk melakukan semua pekerjaan kaki kami! Di bawah ini, melihat--dev beralih, kami memberitahu komposer bahwa kita berada dalam lingkungan pengembangan kami dan bahwa hal itu juga harus menginstal semua dependensi kita terdaftar dalam "memerlukan-dev".

1
composer install --dev

Setelah itu selesai menginstal, kita akan perlu menginformasikan Laravel beberapa dependensi kita. Laravel menggunakan "penyedia jasa" untuk tujuan ini. Penyedia layanan ini pada dasarnya hanya mengatakan kepada Laravel bagaimana kode mereka akan berinteraksi dengan aplikasi dan untuk menjalankan prosedur setup diperlukan. Buka app/config/app.php dan menambahkan dua item berikut ke array "penyedia". Tidak semua paket memerlukan ini, hanya mereka yang akan meningkatkan atau mengubah fungsi Laravel.


App/config/App.php

1
...
2
3
'Way\Generators\GeneratorsServiceProvider',
4
'Conarwelsh\MustacheL4\MustacheL4ServiceProvider',
5
6
...

Terakhir, kita hanya perlu melakukan beberapa tweak aplikasi generik untuk menyelesaikan instalasi Laravel kami. Mari kita membuka bootstrap/start.php dan memberitahu Laravel nama mesin kami sehingga dapat menentukan apa lingkungan yang dalam.


bootstrap/Start.php

1
/*

2
|--------------------------------------------------------------------------

3
| Detect The Application Environment

4
|--------------------------------------------------------------------------

5
|

6
| Laravel takes a dead simple approach to your application environments

7
| so you can just specify a machine name or HTTP host that matches a

8
| given environment, then we will automatically detect it for you.

9
|

10
*/
11
12
$env = $app->detectEnvironment(array(
13
14
  'local' => array('your-machine-name'),
15
16
));

Ganti "your-mesin-name" dengan apa pun yang host untuk mesin Anda. Jika Anda tidak yakin tentang apa nama mesin yang tepat, Anda dapat hanya jenis hostname pada prompt perintah (pada Mac atau Linux), mencetak apa pun merupakan nilai yang dimiliki dalam pengaturan ini.

Kami ingin pandangan kami mampu disajikan kepada klien kami dari permintaan web. Saat ini, pandangan kami disimpan di luar kami folder publik, yang berarti bahwa mereka tidak dapat diakses oleh publik. Untungnya, Laravel membuatnya sangat mudah untuk memindahkan atau menambahkan folder Lihat lainnya. Buka app/config/view.php dan mengubah jalan pengaturan untuk menunjuk ke folder publik kami. Pengaturan ini bekerja seperti pribumi PHP termasuk jalan, itu akan memeriksa di setiap folder sampai menemukan file tampilan pencocokan, sehingga merasa bebas untuk menambahkan beberapa di sini:


App/config/View.php

1
'paths' => array(__DIR__.'/../../public/views'),

Selanjutnya Anda akan perlu untuk mengkonfigurasi database Anda. Buka app/config/database.php dan menambahkan dalam pengaturan database Anda.

Catatan: Dianjurkan untuk menggunakan 127.0.0.1 bukan localhost. Anda mendapatkan sedikit dorongan kinerja pada kebanyakan sistem, dan dengan beberapa konfigurasi sistem, localhost akan tidak bahkan terhubung dengan benar.

Akhirnya, Anda hanya perlu memastikan bahwa folder penyimpanan dapat ditulisi.

1
chmod -R 755 app/storage

Laravel sekarang terinstal, dengan semua dependensi, serta dependensi kita sendiri. Sekarang mari kita setup instalasi tulang punggung kami!

Seperti composer.json kami dipasang semua dependensi sisi server kami, kami akan membuat package.json di folder publik kami untuk menginstal semua dependensi sisi klien kami.

Untuk dependensi sisi klien kami, kami akan menggunakan:

  • Underscore.js: Ini adalah ketergantungan dari Backbone.js, dan toolbelt berguna fungsi.
  • Backbone.js: Ini adalah MVC sisi klien kami bahwa kami akan menggunakan untuk membangun keluar aplikasi kita.
  • Mustache.js: Versi Javascript Perpustakaan template kami, dengan menggunakan bahasa template yang sama baik pada klien dan server, kita dapat berbagi dilihat, sebagai lawan dari duplikasi logika.

Public/Package.JSON

1
{
2
  "name": "nettuts-laravel4-and-backbone",
3
  "version": "0.0.1",
4
  "private": true,
5
  "dependencies": {
6
    "underscore": "*",
7
    "backbone": "*",
8
    "mustache": "*"
9
  }
10
}

Sekarang hanya beralih ke dalam folder publik, dan menjalankan instalasi npm. Setelah itu selesai, memungkinkan beralih kembali ke akar aplikasi kami sehingga kita siap untuk sisa dari kami perintah.

1
cd public
2
npm install
3
cd ..

Manajer paket menyelamatkan kita dari satu ton kerja, jika Anda ingin memperbarui salah satu perpustakaan ini, Semua harus Anda lakukan adalah menjalankan npm update atau update komposer. Juga, jika Anda ingin mengunci salah satu perpustakaan tersebut dalam versi tertentu, Semua harus Anda lakukan adalah menentukan nomor versi, dan manajer paket akan menangani sisanya.

Untuk menyelesaikan proses setup kami kita hanya akan menambahkan dalam semua proyek dasar file dan folder yang akan kita butuhkan, dan kemudian mengujinya untuk memastikan hal itu semua bekerja seperti yang diharapkan.

Kita harus menambahkan folder berikut:

  • masyarakat views
  • Umum/views/layouts
  • Umum/js
  • Umum/css

Dan file-file berikut:

  • Public/CSS/Styles.Less
  • Public/JS/App.js
  • Public/views/App.mustache

Untuk mencapai hal ini, kita dapat menggunakan satu-kapal:

1
mkdir public/views public/views/layouts public/js public/css && touch public/css/styles.less public/js/app.js public/views/app.mustache

Twitter Bootstrap juga memiliki dua dependensi JavaScript yang akan kita butuhkan, jadi mari kita hanya menyalin mereka dari folder Penjual ke folder publik kami. Mereka adalah:

  • html5shiv.js: memungkinkan kita untuk menggunakan unsur-unsur HTML5 tanpa takut browser lama yang tidak mendukung mereka
  • bootstrap.min.js: mendukung JavaScript perpustakaan untuk Twitter Bootstrap
1
cp vendor/twitter/bootstrap/docs/assets/js/html5shiv.js public/js/html5shiv.js
2
cp vendor/twitter/bootstrap/docs/assets/js/bootstrap.min.js public/js/bootstrap.min.js

Untuk file tata letak kami, Twitter Bootstrap juga menyediakan kami dengan beberapa template bagus starter untuk bekerja dengan, jadi mari kita menyalin satu ke folder layout kami untuk kepala mulai:

1
cp vendor/twitter/bootstrap/docs/examples/starter-template.html public/views/layouts/application.blade.php

Perhatikan bahwa saya menggunakan ekstensi pisau di sini, ini bisa sama seperti mudah template kumis, tapi aku ingin menunjukkan kepada Anda betapa mudahnya untuk mencampur mesin template. Karena letak kami akan diberikan pada beban halaman, dan tidak perlu kembali diberikan oleh klien, kami aman untuk menggunakan PHP di sini secara eksklusif. Jika karena alasan tertentu Anda menemukan diri Anda perlu untuk membuat file ini pada sisi klien, Anda akan ingin untuk beralih file ini menggunakan mesin template kumis sebaliknya.

Sekarang bahwa kita memiliki semua file dasar kami di tempat, mari kita menambahkan beberapa konten starter yang bisa kita gunakan untuk menguji bahwa semuanya bekerja seperti yang kita harapkan. Aku menyediakan Anda dengan beberapa Rintisan bertopik dasar untuk Anda mulai.


Public/CSS/Styles.Less

Kami hanya akan mengimpor kericau Bootstrap file dari direktori vendor dibandingkan dengan menyalin mereka. Hal ini memungkinkan kita untuk update Twitter Bootstrap dengan apa-apa kecuali sebuah update komposer.

Kita mendeklarasikan variabel kami pada akhir file, kompilator kurang akan mengetahui nilai semua variabel yang sebelum melakukan penguraian yang kurang ke dalam CSS. Ini berarti bahwa dengan mendefinisikan ulang variabel Twitter Bootstrap pada akhir file, nilai akan benar-benar berubah untuk semua file yang disertakan, yang mengijinkan kita melakukan menimpa sederhana tanpa memodifikasi file inti Twitter Bootstrap.

1
/**

2
 * Import Twitter Bootstrap Base File

3
 ******************************************************************************************

4
 */
5
@import "../../vendor/twitter/bootstrap/less/bootstrap";
6
7
8
/**

9
 * Define App Styles

10
 * Do this before the responsive include, so that it can override properly as needed.

11
 ******************************************************************************************

12
 */
13
body {
14
  padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
15
}
16
17
/* this will set the position of our alerts */
18
#notifications {
19
  width: 300px;
20
  position: fixed;
21
  top: 50px;
22
  left: 50%;
23
  margin-left: -150px;
24
  text-align: center;
25
}
26
27
/**

28
 * Import Bootstrap's Responsive Overrides

29
 * now we allow bootstrap to set the overrides for a responsive layout

30
 ******************************************************************************************

31
 */
32
@import "../../vendor/twitter/bootstrap/less/responsive";
33
34
35
/**

36
 * Define our variables last, any variable declared here will be used in the includes above

37
 * which means that we can override any of the variables used in the bootstrap files easily

38
 * without modifying any of the core bootstrap files

39
 ******************************************************************************************

40
 */
41
42
// Scaffolding
43
// -------------------------
44
@bodyBackground:    #f2f2f2;
45
@textColor:       #575757;
46
47
// Links
48
// -------------------------
49
@linkColor:       #41a096;
50
51
// Typography
52
// -------------------------
53
@sansFontFamily:    Arial, Helvetica, sans-serif;

Public/JS/App.js

Sekarang kita akan bungkus semua kode kami segera-menerapkan--fungsi anonim yang melewati beberapa objek global. Kami akan kemudian alias objek-objek ini global untuk sesuatu yang lebih berguna bagi kami. Juga, kita akan cache beberapa jQuery objek dalam fungsi siap dokumen.

1
//alias the global object

2
//alias jQuery so we can potentially use other libraries that utilize $

3
//alias Backbone to save us on some typing

4
(function(exports, $, bb){
5
6
  //document ready

7
  $(function(){
8
9
    /**

10
     ***************************************

11
     * Cached Globals

12
     ***************************************

13
     */
14
    var $window, $body, $document;
15
16
    $window  = $(window);
17
    $document = $(document);
18
    $body   = $('body');
19
20
21
  });//end document ready

22
23
}(this, jQuery, Backbone));

Public/views/layouts/Application.Blade.php

Berikutnya adalah hanya sebuah sederhana tata letak file HTML. Kami namun menggunakan penolong aset dari Laravel untuk membantu kami dalam membuat jalan untuk aset kami. Ini adalah praktik yang baik untuk menggunakan jenis pembantu, karena jika Anda pernah terjadi untuk memindahkan proyek Anda ke sub-folder, semua link akan masih bekerja.

Kita memastikan bahwa kita termasuk semua dependensi kita dalam file ini, dan juga menambahkan ketergantungan jQuery. Aku memilih untuk meminta jQuery dari Google CDN, karena kemungkinan pengguna mengunjungi situs ini sudah akan memiliki salinan dari CDN yang di-cache dalam browser mereka, menyelamatkan kita dari keharusan untuk menyelesaikan permintaan HTTP untuk itu.

Satu hal penting yang perlu diperhatikan di sini adalah cara di mana kita Apakah bersarang pandangan kami. Kumis tidak memiliki blok bagian seperti pisau melakukannya, sebaliknya, isi dari tampilan bersarang akan dibuat tersedia di bawah sebuah variabel dengan nama bagian. Aku akan menunjuk ini keluar kapan kami memberikan pandangan ini dari rute kami.

1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
 <meta charset="utf-8">
5
 <title>Laravel4 & Backbone | Nettuts</title>
6
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
 <meta name="description" content="A single page blog built using Backbone.js, Laravel, and Twitter Bootstrap">
8
 <meta name="author" content="Conar Welsh">
9
10
 <link href="{{ asset('css/styles.css') }}" rel="stylesheet">
11
12
 <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
13
 <!--[if lt IE 9]>

14
 <script src="{{ asset('js/html5shiv.js') }}"></script>

15
 <![endif]-->
16
</head>
17
<body>
18
19
 <div id="notifications">
20
 </div>
21
22
 <div class="navbar navbar-inverse navbar-fixed-top">
23
  <div class="navbar-inner">
24
   <div class="container">
25
    <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
26
     <span class="icon-bar"></span>
27
     <span class="icon-bar"></span>
28
     <span class="icon-bar"></span>
29
    </button>
30
    <a class="brand" href="#">Nettuts Tutorial</a>
31
    <div class="nav-collapse collapse">
32
     <ul class="nav">
33
      <li class="active"><a href="#">Blog</a></li>
34
     </ul>
35
    </div><!--/.nav-collapse -->
36
   </div>
37
  </div>
38
 </div>
39
40
 <div class="container" data-role="main">
41
  {{--since we are using mustache as the view, it does not have a concept of sections like blade has, so instead of using @yield here, our nested view will just be a variable that we can echo--}}
42
43
  {{ $content }}
44
45
 </div> <!-- /container -->
46
47
 <!-- Placed at the end of the document so the pages load faster -->
48
 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <!-- use Google CDN for jQuery to hopefully get a cached copy -->
49
 <script src="{{ asset('node_modules/underscore/underscore-min.js') }}"></script>
50
 <script src="{{ asset('node_modules/backbone/backbone-min.js') }}"></script>
51
 <script src="{{ asset('node_modules/mustache/mustache.js') }}"></script>
52
 <script src="{{ asset('js/bootstrap.min.js') }}"></script>
53
 <script src="{{ asset('js/app.js') }}"></script>
54
 @yield('scripts')
55
</body>
56
</html>

Public/views/App.mustache

Berikutnya adalah hanya pandangan sederhana yang kita akan sarang menjadi layout kami.

1
<dl>
2
  <dt>Q. What did Biggie say when he watched inception?</dt>
3
  <dd>A. "It was all a dream!"</dd>
4
</dl>

App/Routes.php

Laravel harus sudah menyediakan Anda dengan rute standar, semua yang kita lakukan di sini adalah mengubah nama tampilan yang rute yang akan membuat.

Ingat dari atas, saya katakan bahwa pandangan bersarang akan menjadi tersedia di bawah variabel bernama apa pun adalah bagian orangtua? Nah, ketika Anda sarang pandangan, parameter pertama ke fungsi adalah nama bagian:

1
View::make('view.path')->nest($sectionName, $nestedViewPath, $viewVariables);

Dalam perintah sarang kita kita dipanggil bagian "konten", yang berarti jika kami echo $content dari tata letak kami, kami akan mendapatkan isi diberikan pandangan itu. Jika kita ingin kembali View::make('layouts.application')-> sarang ('foobar', 'apl'); maka kita lihat bersarang akan tersedia di bawah sebuah variabel yang bernama $foobar.

1
<?php
2
3
//backbone app route

4
Route::get('/', function()
5
{
6
  //change our view name to the view we created in a previous step

7
  //notice that we do not need to provide the .mustache extension

8
  return View::make('layouts.application')->nest('content', 'app');
9
});

Dengan semua file kita dasar di tempat, kita dapat menguji untuk memastikan semuanya berjalan OK. Laravel 4 memanfaatkan baru PHP web server untuk menyediakan kami dengan sebuah lingkungan pengembangan kecil yang besar. Begitu lama untuk hari-hari memiliki pengaturan virtual host juta pada mesin pengembangan Anda untuk setiap proyek yang Anda bekerja pada!

Catatan: Pastikan bahwa Anda telah menyusun file Anda kurang pertama!

1
php artisan serve

Jika Anda mengikuti dengan benar, Anda harus tertawa histeris di mengerikan saya rasa humor, dan semua aset kami harus dimasukkan dengan benar ke halaman.


Bagian 2: Laravel 4 JSON API

Sekarang kita akan membangun API yang akan kekuatan aplikasi tulang punggung kami. Laravel 4 membuat ini proses yang mudah.

Pedoman API

Pertama mari kita pergi ke beberapa pedoman umum untuk menjaga dalam pikiran ketika kita membangun API kami:

  • Kode status: Tanggapan harus menjawab dengan kode status yang tepat, melawan godaan untuk hanya menempatkan {kesalahan: "ini adalah pesan galat"} dalam tubuh tanggapan Anda. Menggunakan protokol HTTP penuhnya!

    • 200: sukses
    • 201: sumber daya yang dibuat
    • 204: sukses, tapi tidak ada konten untuk kembali
    • 400: permintaan tidak terpenuhi kesalahan //validation
    • 401: tidak dikonfirmasi
    • 403: penolakan untuk menanggapi //wrong identitasnya, tidak memiliki izin (UN dimiliki sumber daya)
    • 404: tidak ditemukan
    • 500: kesalahan lainnya
  • Metode sumber daya: meskipun controller akan melayani berbagai sumber daya, mereka masih harus memiliki perilaku yang sangat mirip. Lebih diprediksi API Anda adalah, semakin mudah untuk menerapkan dan mengadopsi.

    • Indeks: kembali koleksi sumber.
    • Tampilkan: kembali sumber daya tunggal.
    • membuat: kembalikan formulir. Formulir ini harus detail kolom yang harus diisi, validasi, dan label sebaik mungkin. Serta hal lainnya yang dibutuhkan untuk benar menciptakan sumber daya. Meskipun ini adalah JSON API, hal ini sangat berguna untuk mengembalikan sebuah formulir di sini. Komputer dan seseorang dapat mengurai melalui formulir ini, dan sangat mudah memecahkan item yang diperlukan untuk mengisi successsfully formulir ini. Ini adalah cara yang sangat mudah untuk "dokumen" kebutuhan Anda API.
    • Toko: toko baru sumber daya dan kembali dengan kode status tepat: 201.
    • mengedit: kembalikan formulir diisi dengan keadaan saat ini sumber daya. Formulir ini harus detail kolom yang harus diisi, validasi, dan label sebaik mungkin. Serta hal lainnya yang dibutuhkan untuk benar mengedit sumber daya.
    • Update: Update sumber daya yang ada dan kembali dengan kode status yang tepat.
    • Hapus: menghapus sumber daya yang ada dan kembali dengan kode status tepat: 204.

Routing & versi

API dirancang untuk menjadi sekitar untuk sementara. Hal ini tidak seperti situs web di mana Anda hanya dapat mengubah fungsionalitas di drop dari sepeser pun. Jika Anda memiliki program yang menggunakan API Anda, mereka tidak akan bahagia dengan Anda jika Anda mengubah hal-hal di sekitar dan istirahat program mereka. Untuk alasan ini, sangat penting bahwa Anda menggunakan versi.

Kami selalu dapat membuat "versi dua" dengan tambahan, atau berubah fungsi, dan memungkinkan program kami subscribing untuk opt-in untuk perubahan ini, daripada dipaksakan.

Laravel menyediakan kami dengan kelompok-kelompok rute yang sempurna untuk tempat ini, kode berikut di atas rute pertama kami:

1
<?php
2
3
//create a group of routes that will belong to APIv1

4
Route::group(array('prefix' => 'v1'), function()
5
{
6
  //... insert API routes here...

7
});

Menghasilkan sumber daya

Kita akan menggunakan generator Jeffrey cara untuk menghasilkan sumber daya kami. Ketika kita menghasilkan sumber daya, itu akan membuat item berikut untuk kami:

  • Controller
  • Model
  • Pemandangan (index.blade.php, show.blade.php, create.blade.php, edit.blade.php)
  • Migrasi
  • Benih

Kami hanya akan membutuhkan dua sumber daya untuk aplikasi ini: sumber daya pos dan sumber daya komentar.

Catatan: di update terbaru Generator, saya telah menerima izin kesalahan karena server web saya cara setup. Untuk memperbaiki masalah ini, Anda harus membiarkan hak akses menulis ke folder yang Generator menulis temp file.

1
sudo chmod -R 755 vendor/way/generators/src/Way/

Jalankan perintah menghasilkan: sumber daya

1
php artisan generate:resource post --fields="title:string, content:text, author_name:string"
2
3
php artisan generate:resource comment --fields="content:text, author_name:string, post_id:integer"

Anda sekarang harus berhenti sejenak untuk menyelidiki semua file yang generator menciptakan bagi kita.

Menyesuaikan sumber daya yang dihasilkan

Perintah menghasilkan: sumber daya menyelamatkan kita banyak pekerjaan, tetapi karena kami konfigurasi yang unik, kita masih akan perlu membuat beberapa modifikasi.

Pertama-tama generator ditempatkan pandangan itu diciptakan di folder app dilihat, jadi kita perlu memindahkan mereka ke folder umum dilihat

1
mv app/views/posts public/views/posts
2
mv app/views/comments public/views/comments

App/Routes.php

Kami memutuskan bahwa kami ingin API kami menjadi berversi, jadi kita perlu memindahkan rute yang generator menciptakan kita ke dalam kelompok versi. Kita akan juga ingin namespace controller kami dengan versi yang sesuai, sehingga kita dapat memiliki set yang berbeda untuk setiap versi yang kita membangun. Juga sumber komentar perlu bersarang di bawah sumber posting.

1
<?php
2
3
//create a group of routes that will belong to APIv1

4
Route::group(array('prefix' => 'v1'), function()
5
{
6
  //... insert API routes here...

7
  Route::resource('posts', 'V1\PostsController'); //notice the namespace

8
  Route::resource('posts.comments', 'V1\PostsCommentsController'); //notice the namespace, and the nesting

9
});
10
11
//backbone app route

12
Route::get('/', function()
13
{
14
  //change our view name to the view we created in a previous step

15
  //notice that we do not need to provide the .mustache extension

16
  return View::make('layouts.application')->nest('content', 'app');
17
});

Karena kita ber-namespace controller kami, kami harus memindahkan mereka ke dalam folder mereka sendiri untuk organisasi, mari kita membuat folder bernama V1 dan memindahkan controller kami dihasilkan ke dalamnya. Juga, karena kami bersarang kami controller komentar di bawah posting controller, mari kita mengubah nama dari controller yang mencerminkan hubungan.

1
mkdir app/controllers/V1
2
mv app/controllers/PostsController.php app/controllers/V1/
3
mv app/controllers/CommentsController.php app/controllers/V1/PostsCommentsController.php

Kita akan perlu untuk memperbarui file controller untuk mencerminkan perubahan kami juga. Pertama-tama kita perlu namespace mereka, dan karena mereka ber-namespace, setiap kelas di luar namespace yang akan perlu untuk diimpor secara manual dengan menggunakan pernyataan.

app/controllers/PostsController.php

1
<?php
2
//use our new namespace

3
namespace V1;
4
5
//import classes that are not in this new namespace

6
use BaseController;
7
8
class PostsController extends BaseController {

app/controllers/PostsCommentsController.php

Kita juga perlu untuk memperbarui CommentsController kami dengan nama baru kami: PostsCommentsController

1
<?php
2
//use our new namespace

3
namespace V1;
4
5
//import classes that are not in this new namespace

6
use BaseController;
7
8
//rename our controller class

9
class PostsCommentsController extends BaseController {

Menambahkan dalam repositori

Secara default, repositori bukan merupakan bagian dari Laravel. Laravel ini sangat fleksibel meskipun, dan membuatnya sangat mudah untuk menambahkan mereka. Kita akan menggunakan repositori untuk membantu kita memisahkan logika kami untuk kembali kode kegunaan, serta untuk pengujian. Untuk sekarang kita akan hanya mendapatkan setup untuk menggunakan repositori, kami akan menambahkan pada logika tepat kemudian.

Mari kita membuat folder untuk menyimpan repositori kami di:

1
mkdir app/repositories

Untuk membiarkan kami auto-loader tahu tentang folder baru ini, kita perlu untuk menambahkannya ke file composer.json kami. Lihatlah bagian diperbarui "autoload" file kami, dan Anda akan melihat bahwa kita ditambahkan dalam folder repositori.

Composer.JSON

1
{
2
  "require": {
3
    "laravel/framework": "4.0.*",
4
    "way/generators": "dev-master",
5
    "twitter/bootstrap": "dev-master",
6
    "conarwelsh/mustache-l4": "dev-master"
7
  },
8
  "require-dev": {
9
    "phpunit/phpunit": "3.7.*",
10
    "mockery/mockery": "0.7.*"
11
  },
12
  "autoload": {
13
    "classmap": [
14
      "app/commands",
15
      "app/controllers",
16
      "app/models",
17
      "app/database/migrations",
18
      "app/database/seeds",
19
      "app/tests/TestCase.php",
20
      "app/repositories"
21
    ]
22
  },
23
  "scripts": {
24
    "post-update-cmd": "php artisan optimize"
25
  },
26
  "minimum-stability": "dev"
27
}

Penyemaian Database kami

Benih database adalah alat yang berguna, mereka memberikan kami dengan cara yang mudah untuk mengisi database kami dengan beberapa konten. Generator memberikan kita dengan dasar file untuk pembibitan, kita hanya perlu menambahkan beberapa biji yang sebenarnya.

app/database/seeds/PostsTableSeeder.php

1
<?php
2
3
class PostsTableSeeder extends Seeder {
4
5
  public function run()
6
  {
7
    $posts = array(
8
      array(
9
        'title'    => 'Test Post',
10
        'content'   => 'Lorem ipsum Reprehenderit velit est irure in enim in magna aute occaecat qui velit ad.',
11
        'author_name' => 'Conar Welsh',
12
        'created_at' => date('Y-m-d H:i:s'),
13
        'updated_at' => date('Y-m-d H:i:s'),
14
      ),
15
      array(
16
        'title'    => 'Another Test Post',
17
        'content'   => 'Lorem ipsum Reprehenderit velit est irure in enim in magna aute occaecat qui velit ad.',
18
        'author_name' => 'Conar Welsh',
19
        'created_at' => date('Y-m-d H:i:s'),
20
        'updated_at' => date('Y-m-d H:i:s'),
21
      ),
22
    );
23
24
    // Uncomment the below to run the seeder

25
    DB::table('posts')->insert($posts);
26
  }
27
28
}

app/database/seeds/CommentsTableSeeder.php

1
<?php
2
3
class CommentsTableSeeder extends Seeder {
4
5
  public function run()
6
  {
7
    $comments = array(
8
      array(
9
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
10
        'author_name' => 'Testy McTesterson',
11
        'post_id'   => 1,
12
        'created_at' => date('Y-m-d H:i:s'),
13
        'updated_at' => date('Y-m-d H:i:s'),
14
      ),
15
      array(
16
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
17
        'author_name' => 'Testy McTesterson',
18
        'post_id'   => 1,
19
        'created_at' => date('Y-m-d H:i:s'),
20
        'updated_at' => date('Y-m-d H:i:s'),
21
      ),
22
      array(
23
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
24
        'author_name' => 'Testy McTesterson',
25
        'post_id'   => 2,
26
        'created_at' => date('Y-m-d H:i:s'),
27
        'updated_at' => date('Y-m-d H:i:s'),
28
      ),
29
    );
30
31
    // Uncomment the below to run the seeder

32
    DB::table('comments')->insert($comments);
33
  }
34
35
}

Jangan lupa untuk menjalankan komposer dump-autoload untuk membiarkan komposer auto loader tahu tentang file migrasi baru!

1
composer dump-autoload

Sekarang kita dapat menjalankan migrasi dan benih kami database. Laravel menyediakan kami dengan satu perintah untuk melakukan keduanya:

1
php artisan migrate --seed

Tes

Pengujian adalah salah satu topik tersebut dalam pengembangan yang tidak ada yang bisa berdebat pentingnya, namun kebanyakan orang cenderung untuk mengabaikan hal itu karena kurva belajar. Pengujian benar-benar tidak sulit dan dapat secara dramatis meningkatkan aplikasi Anda. Untuk tutorial ini, kita akan setup beberapa tes dasar untuk membantu kami memastikan bahwa API kami berfungsi dengan baik. Kita akan membangun gaya API TDD ini. Aturan TDD menyatakan bahwa kita tidak diperbolehkan untuk menulis kode produksi sampai kita telah gagal tes yang menjamin itu. Namun, jika saya untuk memandu Anda melalui setiap tes secara individual, ini akan membuktikan untuk menjadi sebuah tutorial yang sangat panjang, sehingga kepentingan keringkasannya, aku hanya akan menyediakan Anda dengan beberapa tes untuk bekerja dari, dan kemudian kode yang benar untuk membuat mereka tes lulus sesudahnya.

Sebelum kita menulis tes apapun meskipun, kita harus terlebih dahulu memeriksa status pengujian saat ini aplikasi kita. Karena kita diinstal PHPUnit melalui komposer, kami memiliki binari tersedia bagi kita untuk menggunakan. Semua yang perlu Anda lakukan adalah menjalankan:

1
vendor/phpunit/phpunit/phpunit.php

Whoops! Kita sudah memiliki kegagalan! Ujian yang gagal adalah benar-benar sebuah contoh tes yang datang pra-instal di struktur aplikasi Laravel kami, tes ini terhadap route default yang juga diinstal dengan struktur aplikasi Laravel. Karena kami diubah rute ini, kita tidak bisa terkejut bahwa tes gagal. Kita bisa Namun, hanya menghapus tes ini sama sekali karena tidak berlaku untuk aplikasi kita.

1
rm app/tests/ExampleTest.php

Jika Anda menjalankan perintah PHPUnit lagi, Anda akan melihat bahwa tidak ada tes dieksekusi, dan kami memiliki yang bersih untuk pengujian.

Catatan: mungkin bahwa jika Anda memiliki versi dari Jeffrey cara generator yang Anda benar-benar akan memiliki beberapa tes di sana yang diciptakan oleh orang Generator, dan tes tersebut mungkin gagal. Hanya menghapus atau menimpa tes itu dengan yang ditemukan di bawah ini untuk melanjutkan.

Untuk tutorial ini kita akan menguji controller kami dan kami repositori. Mari kita membuat beberapa folder untuk menyimpan tes ini di:

1
mkdir app/tests/controllers app/tests/repositories

Sekarang untuk test file. Kita akan menggunakan ejekan untuk mengejek kami repositori untuk pengujian controller kami. Objek olok-olok lakukan seperti namanya mereka, mereka "mengejek" objek dan melaporkan kembali kepada kami pada bagaimana objek-objek yang berinteraksi dengan.

Dalam kasus tes controller, kita tidak benar-benar ingin repositori untuk dipanggil, setelah semua, ini adalah tes controller, tidak tes repositori. Jadi ejekan akan mengatur kita objek untuk menggunakan bukan repositori kami, dan biarkan kami tahu apakah objek tersebut dipanggil sebagai kami mengharapkan mereka.

Untuk tarik ini, kita akan memiliki untuk memberitahu controller menggunakan benda kami "diejek" bukan hal yang nyata. Kami hanya akan memberitahu aplikasi kita untuk menggunakan contoh yang diejek waktu berikutnya kelas tertentu yang diminta. Perintah seperti ini:

1
App::instance($classToReplace, $instanceOfClassToReplaceWith);

Proses mengejek secara keseluruhan akan pergi sesuatu seperti ini:

  • Membuat objek olok-olok baru, memberikan nama kelas yang untuk mengejek.
  • Memberitahu ejekan objek metode yang itu harus mengharapkan untuk menerima, berapa kali ini harus menerima metode, dan apa metode yang harus kembali.
  • Gunakan perintah yang ditunjukkan di atas untuk memberitahu aplikasi kita untuk menggunakan objek olok-olok baru ini bukan default.
  • Menjalankan controller metode seperti biasa.
  • Menyatakan respon.

app/tests/controllers/CommentsControllerTest.php

1
<?php
2
3
class CommentsControllerTest extends TestCase {
4
5
  /**

6
   ************************************************************************

7
   * Basic Route Tests

8
   * notice that we can use our route() helper here!

9
   ************************************************************************

10
   */
11
12
  //test that GET /v1/posts/1/comments returns HTTP 200

13
  public function testIndex()
14
  {
15
    $response = $this->call('GET', route('v1.posts.comments.index', array(1)) );
16
    $this->assertTrue($response->isOk());
17
  }
18
19
  //test that GET /v1/posts/1/comments/1 returns HTTP 200

20
  public function testShow()
21
  {
22
    $response = $this->call('GET', route('v1.posts.comments.show', array(1,1)) );
23
    $this->assertTrue($response->isOk());
24
  }
25
26
  //test that GET /v1/posts/1/comments/create returns HTTP 200

27
  public function testCreate()
28
  {
29
    $response = $this->call('GET', route('v1.posts.comments.create', array(1)) );
30
    $this->assertTrue($response->isOk());
31
  }
32
33
  //test that GET /v1/posts/1/comments/1/edit returns HTTP 200

34
  public function testEdit()
35
  {
36
    $response = $this->call('GET', route('v1.posts.comments.edit', array(1,1)) );
37
    $this->assertTrue($response->isOk());
38
  }
39
40
  /**

41
   *************************************************************************

42
   * Tests to ensure that the controller calls the repo as we expect

43
   * notice we are "Mocking" our repository

44
   *

45
   * also notice that we do not really care about the data or interactions

46
   * we merely care that the controller is doing what we are going to want

47
   * it to do, which is reach out to our repository for more information

48
   *************************************************************************

49
   */
50
51
  //ensure that the index function calls our repository's "findAll" method

52
  public function testIndexShouldCallFindAllMethod()
53
  {
54
    //create our new Mockery object with a name of CommentRepositoryInterface

55
    $mock = Mockery::mock('CommentRepositoryInterface');
56
57
    //inform the Mockery object that the "findAll" method should be called on it once

58
    //and return a string value of "foo"

59
    $mock->shouldReceive('findAll')->once()->andReturn('foo');
60
61
    //inform our application that we have an instance that it should use

62
    //whenever the CommentRepositoryInterface is requested

63
    App::instance('CommentRepositoryInterface', $mock);
64
65
    //call our controller route

66
    $response = $this->call('GET', route('v1.posts.comments.index', array(1)));
67
68
    //assert that the response is a boolean value of true

69
    $this->assertTrue(!! $response->original);
70
  }
71
72
  //ensure that the show method calls our repository's "findById" method

73
  public function testShowShouldCallFindById()
74
  {
75
    $mock = Mockery::mock('CommentRepositoryInterface');
76
    $mock->shouldReceive('findById')->once()->andReturn('foo');
77
    App::instance('CommentRepositoryInterface', $mock);
78
79
    $response = $this->call('GET', route('v1.posts.comments.show', array(1,1)));
80
    $this->assertTrue(!! $response->original);
81
  }
82
83
  //ensure that our create method calls the "instance" method on the repository

84
  public function testCreateShouldCallInstanceMethod()
85
  {
86
    $mock = Mockery::mock('CommentRepositoryInterface');
87
    $mock->shouldReceive('instance')->once()->andReturn(array());
88
    App::instance('CommentRepositoryInterface', $mock);
89
90
    $response = $this->call('GET', route('v1.posts.comments.create', array(1)));
91
    $this->assertViewHas('comment');
92
  }
93
94
  //ensure that the edit method calls our repository's "findById" method

95
  public function testEditShouldCallFindByIdMethod()
96
  {
97
    $mock = Mockery::mock('CommentRepositoryInterface');
98
    $mock->shouldReceive('findById')->once()->andReturn(array());
99
    App::instance('CommentRepositoryInterface', $mock);
100
101
    $response = $this->call('GET', route('v1.posts.comments.edit', array(1,1)));
102
    $this->assertViewHas('comment');
103
  }
104
105
  //ensure that the store method should call the repository's "store" method

106
  public function testStoreShouldCallStoreMethod()
107
  {
108
    $mock = Mockery::mock('CommentRepositoryInterface');
109
    $mock->shouldReceive('store')->once()->andReturn('foo');
110
    App::instance('CommentRepositoryInterface', $mock);
111
112
    $response = $this->call('POST', route('v1.posts.comments.store', array(1)));
113
    $this->assertTrue(!! $response->original);
114
  }
115
116
  //ensure that the update method should call the repository's "update" method

117
  public function testUpdateShouldCallUpdateMethod()
118
  {
119
    $mock = Mockery::mock('CommentRepositoryInterface');
120
    $mock->shouldReceive('update')->once()->andReturn('foo');
121
    App::instance('CommentRepositoryInterface', $mock);
122
123
    $response = $this->call('PUT', route('v1.posts.comments.update', array(1,1)));
124
    $this->assertTrue(!! $response->original);
125
  }
126
127
  //ensure that the destroy method should call the repositories "destroy" method

128
  public function testDestroyShouldCallDestroyMethod()
129
  {
130
    $mock = Mockery::mock('CommentRepositoryInterface');
131
    $mock->shouldReceive('destroy')->once()->andReturn(true);
132
    App::instance('CommentRepositoryInterface', $mock);
133
134
    $response = $this->call('DELETE', route('v1.posts.comments.destroy', array(1,1)));
135
    $this->assertTrue( empty($response->original) );
136
  }
137
138
139
}

app/tests/controllers/PostsControllerTest.php

Selanjutnya, kita akan mengikuti prosedur yang tepat sama untuk tes PostsController

1
<?php
2
3
class PostsControllerTest extends TestCase {
4
5
  /**

6
   * Test Basic Route Responses

7
   */
8
  public function testIndex()
9
  {
10
    $response = $this->call('GET', route('v1.posts.index'));
11
    $this->assertTrue($response->isOk());
12
  }
13
14
  public function testShow()
15
  {
16
    $response = $this->call('GET', route('v1.posts.show', array(1)));
17
    $this->assertTrue($response->isOk());
18
  }
19
20
  public function testCreate()
21
  {
22
    $response = $this->call('GET', route('v1.posts.create'));
23
    $this->assertTrue($response->isOk());
24
  }
25
26
  public function testEdit()
27
  {
28
    $response = $this->call('GET', route('v1.posts.edit', array(1)));
29
    $this->assertTrue($response->isOk());
30
  }
31
32
  /**

33
   * Test that controller calls repo as we expect

34
   */
35
  public function testIndexShouldCallFindAllMethod()
36
  {
37
    $mock = Mockery::mock('PostRepositoryInterface');
38
    $mock->shouldReceive('findAll')->once()->andReturn('foo');
39
    App::instance('PostRepositoryInterface', $mock);
40
41
    $response = $this->call('GET', route('v1.posts.index'));
42
    $this->assertTrue(!! $response->original);
43
  }
44
45
  public function testShowShouldCallFindById()
46
  {
47
    $mock = Mockery::mock('PostRepositoryInterface');
48
    $mock->shouldReceive('findById')->once()->andReturn('foo');
49
    App::instance('PostRepositoryInterface', $mock);
50
51
    $response = $this->call('GET', route('v1.posts.show', array(1)));
52
    $this->assertTrue(!! $response->original);
53
  }
54
55
  public function testCreateShouldCallInstanceMethod()
56
  {
57
    $mock = Mockery::mock('PostRepositoryInterface');
58
    $mock->shouldReceive('instance')->once()->andReturn(array());
59
    App::instance('PostRepositoryInterface', $mock);
60
61
    $response = $this->call('GET', route('v1.posts.create'));
62
    $this->assertViewHas('post');
63
  }
64
65
  public function testEditShouldCallFindByIdMethod()
66
  {
67
    $mock = Mockery::mock('PostRepositoryInterface');
68
    $mock->shouldReceive('findById')->once()->andReturn(array());
69
    App::instance('PostRepositoryInterface', $mock);
70
71
    $response = $this->call('GET', route('v1.posts.edit', array(1)));
72
    $this->assertViewHas('post');
73
  }
74
75
  public function testStoreShouldCallStoreMethod()
76
  {
77
    $mock = Mockery::mock('PostRepositoryInterface');
78
    $mock->shouldReceive('store')->once()->andReturn('foo');
79
    App::instance('PostRepositoryInterface', $mock);
80
81
    $response = $this->call('POST', route('v1.posts.store'));
82
    $this->assertTrue(!! $response->original);
83
  }
84
85
  public function testUpdateShouldCallUpdateMethod()
86
  {
87
    $mock = Mockery::mock('PostRepositoryInterface');
88
    $mock->shouldReceive('update')->once()->andReturn('foo');
89
    App::instance('PostRepositoryInterface', $mock);
90
91
    $response = $this->call('PUT', route('v1.posts.update', array(1)));
92
    $this->assertTrue(!! $response->original);
93
  }
94
95
  public function testDestroyShouldCallDestroyMethod()
96
  {
97
    $mock = Mockery::mock('PostRepositoryInterface');
98
    $mock->shouldReceive('destroy')->once()->andReturn(true);
99
    App::instance('PostRepositoryInterface', $mock);
100
101
    $response = $this->call('DELETE', route('v1.posts.destroy', array(1)));
102
    $this->assertTrue( empty($response->original) );
103
  }
104
105
}

app/tests/repositories/EloquentCommentRepositoryTest.php

Sekarang untuk tes repositori. Dalam menulis tes controller kami, kami cukup banyak sudah memutuskan apa yang sebagian besar antarmuka akan terlihat seperti untuk repositori. Controller kami diperlukan metode berikut:

  • findById($id)
  • findAll()
  • instance($data)
  • Store($data)
  • Update ($id, $data)
  • Destroy($id)

Hanya metode lainnya yang kita akan ingin menambahkan di sini adalah metode validasi. Ini terutama akan menjadi metode pribadi untuk repositori untuk memastikan bahwa data aman untuk menyimpan atau update.

Tes ini, kita juga akan ditambahkan metode setUp, yang akan memungkinkan kita untuk menjalankan beberapa kode di kelas kami, sebelum pelaksanaan setiap tes. Metode setUp kami akan sangat sederhana, kami hanya memastikan bahwa setiap metode setUp yang didefinisikan di kelas induk juga disebut menggunakan parent:: setUp() dan kemudian menambahkan variabel kelas yang menyimpan instance dari repositori kami.

Kita akan menggunakan kekuatan Laravel's IoC wadah lagi untuk mendapatkan instance dari repositori kami. Perintah App::make() akan mengembalikan instance kelas diminta, sekarang tampaknya aneh bahwa kami tidak hanya melakukan $this-> repo = EloquentCommentRepository() baru, namun terus pikiran itu, kita akan kembali ke sana sejenak. Anda mungkin menyadari bahwa kami meminta untuk kelas yang disebut EloquentCommentRepository, tetapi dalam tes controller kami di atas, kita memanggil kami repositori CommentRepositoryInterface... menempatkan ini berpikir di belakang-kompor serta... explainations untuk keduanya datang, aku berjanji!

1
<?php
2
3
class EloquentCommentRepositoryTest extends TestCase {
4
5
  public function setUp()
6
  {
7
    parent::setUp();
8
    $this->repo = App::make('EloquentCommentRepository');
9
  }
10
11
  public function testFindByIdReturnsModel()
12
  {
13
    $comment = $this->repo->findById(1,1);
14
    $this->assertTrue($comment instanceof Illuminate\Database\Eloquent\Model);
15
  }
16
17
  public function testFindAllReturnsCollection()
18
  {
19
    $comments = $this->repo->findAll(1);
20
    $this->assertTrue($comments instanceof Illuminate\Database\Eloquent\Collection);
21
  }
22
23
  public function testValidatePasses()
24
  {
25
    $reply = $this->repo->validate(array(
26
      'post_id'   => 1,
27
      'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.',
28
      'author_name' => 'Testy McTesterson'
29
    ));
30
31
    $this->assertTrue($reply);
32
  }
33
34
  public function testValidateFailsWithoutContent()
35
  {
36
    try {
37
      $reply = $this->repo->validate(array(
38
        'post_id'   => 1,
39
        'author_name' => 'Testy McTesterson'
40
      ));
41
    }
42
    catch(ValidationException $expected)
43
    {
44
      return;
45
    }
46
47
    $this->fail('ValidationException was not raised');
48
  }
49
50
  public function testValidateFailsWithoutAuthorName()
51
  {
52
    try {
53
      $reply = $this->repo->validate(array(
54
        'post_id'   => 1,
55
        'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.'
56
      ));
57
    }
58
    catch(ValidationException $expected)
59
    {
60
      return;
61
    }
62
63
    $this->fail('ValidationException was not raised');
64
  }
65
66
  public function testValidateFailsWithoutPostId()
67
  {
68
    try {
69
      $reply = $this->repo->validate(array(
70
        'author_name' => 'Testy McTesterson',
71
        'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.'
72
      ));
73
    }
74
    catch(ValidationException $expected)
75
    {
76
      return;
77
    }
78
79
    $this->fail('ValidationException was not raised');
80
  }
81
82
  public function testStoreReturnsModel()
83
  {
84
    $comment_data = array(
85
      'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.',
86
      'author_name' => 'Testy McTesterson'
87
    );
88
89
    $comment = $this->repo->store(1, $comment_data);
90
91
    $this->assertTrue($comment instanceof Illuminate\Database\Eloquent\Model);
92
    $this->assertTrue($comment->content === $comment_data['content']);
93
    $this->assertTrue($comment->author_name === $comment_data['author_name']);
94
  }
95
96
  public function testUpdateSaves()
97
  {
98
    $comment_data = array(
99
      'content' => 'The Content Has Been Updated'
100
    );
101
102
    $comment = $this->repo->update(1, 1, $comment_data);
103
104
    $this->assertTrue($comment instanceof Illuminate\Database\Eloquent\Model);
105
    $this->assertTrue($comment->content === $comment_data['content']);
106
  }
107
108
  public function testDestroySaves()
109
  {
110
    $reply = $this->repo->destroy(1,1);
111
    $this->assertTrue($reply);
112
113
    try {
114
      $this->repo->findById(1,1);
115
    }
116
    catch(NotFoundException $expected)
117
    {
118
      return;
119
    }
120
121
    $this->fail('NotFoundException was not raised');
122
  }
123
124
  public function testInstanceReturnsModel()
125
  {
126
    $comment = $this->repo->instance();
127
    $this->assertTrue($comment instanceof Illuminate\Database\Eloquent\Model);
128
  }
129
130
  public function testInstanceReturnsModelWithData()
131
  {
132
    $comment_data = array(
133
      'title' => 'Un-validated title'
134
    );
135
136
    $comment = $this->repo->instance($comment_data);
137
    $this->assertTrue($comment instanceof Illuminate\Database\Eloquent\Model);
138
    $this->assertTrue($comment->title === $comment_data['title']);
139
  }
140
141
}

app/tests/repositories/EloquentPostRepositoryTest.php

1
<?php
2
3
class EloquentPostRepositoryTest extends TestCase {
4
5
  public function setUp()
6
  {
7
    parent::setUp();
8
    $this->repo = App::make('EloquentPostRepository');
9
  }
10
11
  public function testFindByIdReturnsModel()
12
  {
13
    $post = $this->repo->findById(1);
14
    $this->assertTrue($post instanceof Illuminate\Database\Eloquent\Model);
15
  }
16
17
  public function testFindAllReturnsCollection()
18
  {
19
    $posts = $this->repo->findAll();
20
    $this->assertTrue($posts instanceof Illuminate\Database\Eloquent\Collection);
21
  }
22
23
  public function testValidatePasses()
24
  {
25
    $reply = $this->repo->validate(array(
26
      'title'    => 'This Should Pass',
27
      'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.',
28
      'author_name' => 'Testy McTesterson'
29
    ));
30
31
    $this->assertTrue($reply);
32
  }
33
34
  public function testValidateFailsWithoutTitle()
35
  {
36
    try {
37
      $reply = $this->repo->validate(array(
38
        'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.',
39
        'author_name' => 'Testy McTesterson'
40
      ));
41
    }
42
    catch(ValidationException $expected)
43
    {
44
      return;
45
    }
46
47
    $this->fail('ValidationException was not raised');
48
  }
49
50
  public function testValidateFailsWithoutAuthorName()
51
  {
52
    try {
53
      $reply = $this->repo->validate(array(
54
        'title'    => 'This Should Pass',
55
        'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.'
56
      ));
57
    }
58
    catch(ValidationException $expected)
59
    {
60
      return;
61
    }
62
63
    $this->fail('ValidationException was not raised');
64
  }
65
66
  public function testStoreReturnsModel()
67
  {
68
    $post_data = array(
69
      'title'    => 'This Should Pass',
70
      'content'   => 'Lorem ipsum Fugiat consectetur laborum Ut consequat aliqua.',
71
      'author_name' => 'Testy McTesterson'
72
    );
73
74
    $post = $this->repo->store($post_data);
75
76
    $this->assertTrue($post instanceof Illuminate\Database\Eloquent\Model);
77
    $this->assertTrue($post->title === $post_data['title']);
78
    $this->assertTrue($post->content === $post_data['content']);
79
    $this->assertTrue($post->author_name === $post_data['author_name']);
80
  }
81
82
  public function testUpdateSaves()
83
  {
84
    $post_data = array(
85
      'title' => 'The Title Has Been Updated'
86
    );
87
88
    $post = $this->repo->update(1, $post_data);
89
90
    $this->assertTrue($post instanceof Illuminate\Database\Eloquent\Model);
91
    $this->assertTrue($post->title === $post_data['title']);
92
  }
93
94
  public function testDestroySaves()
95
  {
96
    $reply = $this->repo->destroy(1);
97
    $this->assertTrue($reply);
98
99
    try {
100
      $this->repo->findById(1);
101
    }
102
    catch(NotFoundException $expected)
103
    {
104
      return;
105
    }
106
107
    $this->fail('NotFoundException was not raised');
108
  }
109
110
  public function testInstanceReturnsModel()
111
  {
112
    $post = $this->repo->instance();
113
    $this->assertTrue($post instanceof Illuminate\Database\Eloquent\Model);
114
  }
115
116
  public function testInstanceReturnsModelWithData()
117
  {
118
    $post_data = array(
119
      'title' => 'Un-validated title'
120
    );
121
122
    $post = $this->repo->instance($post_data);
123
    $this->assertTrue($post instanceof Illuminate\Database\Eloquent\Model);
124
    $this->assertTrue($post->title === $post_data['title']);
125
  }
126
127
}

Sekarang bahwa kita memiliki semua pengujian kami di tempat, mari kita jalankan PHPUnit lagi untuk menonton mereka gagal!

1
vendor/phpunit/phpunit/phpunit.php

Anda harus memiliki seluruh ton kegagalan, dan pada kenyataannya, suite tes mungkin tidak bahkan tamat pengujian sebelum jatuh. Ini adalah OK, berarti kita telah mengikuti aturan TDD dan menulis gagal tes sebelum kode produksi. Meskipun, biasanya tes ini akan ditulis satu per satu waktu dan Anda tidak akan melanjutkan untuk tes berikutnya sampai Anda memiliki kode yang diperbolehkan tes sebelumnya untuk lulus. Terminal Anda mungkin harus terlihat seperti tambang saat:

Screenshot

Apa yang benar-benar gagal adalah metode assertViewHas dalam tes controller kami. Memang agak mengintimidasi untuk berurusan dengan jenis kesalahan ketika kita memiliki dikelompok bersama semua pengujian kami tanpa produksi setiap kode sama sekali. Inilah sebabnya mengapa Anda harus selalu menulis tes satu pada satu waktu, karena Anda akan menemukan kesalahan ini dengan tenang, bukan hanya kekacauan besar kesalahan sekaligus. Untuk saat ini, hanya mengikuti memimpin saya ke implementasi kode kita.


Diskusi sidebar

Sebelum kita melanjutkan dengan implementasi, mari kita istirahat untuk cepat sidebar diskusi tentang tanggung jawab dari pola MVC.

Dari geng empat:

Model adalah objek aplikasi, yang adalah presentasi layar, dan Controller mendefinisikan cara bereaksi antarmuka pengguna untuk input pengguna.

Titik menggunakan struktur seperti ini adalah untuk tetap dikemas dan fleksibel, memungkinkan kita untuk pertukaran dan penggunaan kembali komponen. Mari kita pergi melalui setiap bagian dari pola MVC dan berbicara tentang usabilitas dan fleksibilitas:

Pemandangan

Saya pikir sebagian besar orang akan setuju bahwa pandangan ini seharusnya menjadi representasi visual sederhana data dan tidak boleh mengandung banyak logika. Dalam kasus kami, sebagai pengembang untuk web, pandangan kita cenderung menjadi HTML atau XML.

  • Reusable: selalu, hampir apa pun yang dapat membuat tampilan
  • fleksibel: tidak memiliki logika apapun nyata dalam lapisan-lapisan ini membuat ini sangat fleksibel

Controller

Jika Controller "mendefinisikan cara bereaksi antarmuka pengguna untuk input pengguna", maka tanggung jawab harus mendengarkan input pengguna (Dapatkan, posting, header, dll), dan membangun keadaan saat ini aplikasi. Menurut pendapat saya, Controller harus sangat ringan dan tidak boleh mengandung kode lebih daripada yang diperlukan untuk mencapai di atas.

  • Reusable: kita harus ingat bahwa controller kami kembali pandangan dogmatis, sehingga kita tidak pernah memanggil metode Controller dalam cara yang praktis untuk menggunakan salah satu logika di dalamnya. Oleh karena itu logika apapun yang ditempatkan dalam Controller metode, harus spesifik untuk metode Controller, jika logika dapat digunakan kembali, itu harus ditempatkan di tempat lain.
  • fleksibel: dalam kebanyakan PHP MVCs Controller terkait langsung dengan rute, yang tidak meninggalkan kita fleksibilitas sangat banyak. Laravel perbaikan masalah ini dengan memungkinkan kita untuk menyatakan rute yang menggunakan controller, sehingga kita sekarang dapat swap keluar controller kami dengan implementasi yang berbeda jika perlu:
1
Route::get('/', array(
2
  'uses' => 'SomeController@action'
3
));

Model

Model adalah "objek aplikasi" dalam definisi kita dari Gang of Four. Ini adalah definisi yang sangat generik. Selain itu, kami hanya memutuskan untuk offload logika apapun yang dapat digunakan kembali dari Controller kami, dan karena Model adalah satu-satunya komponen yang tersisa dalam struktur didefinisikan kita, logis untuk mengasumsikan bahwa ini adalah rumah baru untuk logika. Namun, saya pikir Model tidak boleh mengandung logika apapun seperti ini. Menurut pendapat saya, kita harus berpikir tentang kami "objek aplikasi", dalam hal ini sebagai objek yang mewakili tempatnya dalam lapisan data, apakah itu sebuah tabel, baris, atau koleksi sepenuhnya tergantung pada negara. Model harus berisi tidak lebih dari Getter dan setter untuk data (termasuk hubungan).

  • Reusable: jika kita mengikuti praktek di atas dan membuat model kami menjadi sebuah obyek yang mewakili tempatnya dalam database, objek ini tetap sangat dapat digunakan kembali. Setiap bagian dari sistem kami dapat menggunakan model ini dan dengan melakukan begitu mendapatkan lengkap dan unopinionated akses ke database.
  • fleksibel: mengikuti praktek di atas, Model kami adalah pada dasarnya penerapan ORM, ini memungkinkan kita untuk menjadi fleksibel, karena kita sekarang memiliki kekuatan untuk mengubah ORM's setiap kali kita ingin hanya dengan menambahkan Model baru. Kita mungkin harus memiliki antarmuka yang ditetapkan yang Model kita harus mematuhi, seperti: semua, menemukan, membuat, memperbarui, menghapus. Pelaksanaan ORM baru akan menjadi sederhana seperti memastikan bahwa antarmuka disebutkan sebelumnya adalah lobi.

Repositori

Hanya dengan hati-hati menentukan komponen MVC kami, kami yatim semua jenis logika ke dalam tanah tak bertuan. Ini adalah di mana repositori datang untuk mengisi kekosongan. Repositori menjadi perantara controller dan model. Permintaan khas akan menjadi sesuatu seperti ini:

  • Controller menerima semua input pengguna dan menyalurkannya ke repositori.
  • Repositori melakukan tindakan "pra-mengumpulkan" seperti validasi data, otorisasi, otentikasi, dll. Jika tindakan "pra-berkumpul" ini berhasil, maka permintaan dilewatkan ke Model untuk diproses.
  • Model akan memproses semua data ke dalam lapisan data, dan kembali keadaan saat ini.
  • Repositori akan menangani apapun rutinitas "pasca-gathering" dan kembali keadaan saat ini ke controller.
  • Controller ini kemudian akan menciptakan tampilan yang tepat menggunakan informasi yang disediakan oleh repositori.

Kami repositori berakhir sebagai fleksibel dan terorganisir seperti kita telah membuat kami controller dan model, memungkinkan kita untuk menggunakan kembali ini di sebagian besar sistem kami, serta mampu untuk swap keluar untuk implementasi lain jika diperlukan.

Kita telah melihat contoh menukar repositori untuk implementasi lain dalam tes Controller di atas. Alih-alih menggunakan default kami repositori, kami meminta wadah IoC untuk menyediakan controller dengan instance objek olok-olok. Kami memiliki kekuatan yang sama ini untuk semua komponen kami.

Apa kami telah accomplised di sini dengan menambahkan lapisan lain MVC kami, adalah sistem yang sangat terorganisir, terukur dan diuji. Mari kita mulai menempatkan potongan-potongan di tempat dan mendapatkan kami tes untuk lulus.


Implementasi controller

Jika Anda mengambil membaca melalui tes controller, Anda akan melihat bahwa kita benar-benar peduli tentang adalah bagaimana controller berinteraksi dengan repositori. Jadi mari kita melihat betapa ringan dan sederhana yang membuat controller kami.

Catatan: dalam TDD, tujuannya adalah untuk tidak lagi bekerja dari yang diperlukan untuk membuat tes Anda lulus. Jadi kita ingin melakukan mutlak minimal di sini.

app/controllers/V1/PostsController.php

1
<?php
2
namespace V1;
3
4
use BaseController; 
5
use PostRepositoryInterface; 
6
use Input;
7
use View;
8
9
class PostsController extends BaseController {
10
11
  /**

12
   * We will use Laravel's dependency injection to auto-magically

13
   * "inject" our repository instance into our controller

14
   */
15
  public function __construct(PostRepositoryInterface $posts)
16
  {
17
    $this->posts = $posts;
18
  }
19
20
  /**

21
   * Display a listing of the resource.

22
   *

23
   * @return Response

24
   */
25
  public function index()
26
  {
27
    return $this->posts->findAll();
28
  }
29
30
  /**

31
   * Show the form for creating a new resource.

32
   *

33
   * @return Response

34
   */
35
  public function create()
36
  {
37
    $post = $this->posts->instance();
38
    return View::make('posts._form', compact('post'));
39
  }
40
41
  /**

42
   * Store a newly created resource in storage.

43
   *

44
   * @return Response

45
   */
46
  public function store()
47
  {
48
    return $this->posts->store( Input::all() );
49
  }
50
51
  /**

52
   * Display the specified resource.

53
   *

54
   * @param int $id

55
   * @return Response

56
   */
57
  public function show($id)
58
  {
59
    return $this->posts->findById($id);
60
  }
61
62
  /**

63
   * Show the form for editing the specified resource.

64
   *

65
   * @param int $id

66
   * @return Response

67
   */
68
  public function edit($id)
69
  {
70
    $post = $this->posts->findById($id);
71
    return View::make('posts._form', compact('post'));
72
  }
73
74
  /**

75
   * Update the specified resource in storage.

76
   *

77
   * @param int $id

78
   * @return Response

79
   */
80
  public function update($id)
81
  {
82
    return $this->posts->update($id, Input::all());
83
  }
84
85
  /**

86
   * Remove the specified resource from storage.

87
   *

88
   * @param int $id

89
   * @return Response

90
   */
91
  public function destroy($id)
92
  {
93
    $this->posts->destroy($id);
94
    return '';
95
  }
96
97
}

app/controllers/PostsCommentsController.php

1
<?php
2
namespace V1;
3
4
use BaseController; 
5
use CommentRepositoryInterface; 
6
use Input;
7
use View;
8
9
class PostsCommentsController extends BaseController {
10
11
  /**

12
   * We will use Laravel's dependency injection to auto-magically

13
   * "inject" our repository instance into our controller

14
   */
15
  public function __construct(CommentRepositoryInterface $comments)
16
  {
17
    $this->comments = $comments;
18
  }
19
20
  /**

21
   * Display a listing of the resource.

22
   *

23
   * @return Response

24
   */
25
  public function index($post_id)
26
  {
27
    return $this->comments->findAll($post_id);
28
  }
29
30
  /**

31
   * Show the form for creating a new resource.

32
   *

33
   * @return Response

34
   */
35
  public function create($post_id)
36
  {
37
    $comment = $this->comments->instance(array(
38
      'post_id' => $post_id
39
    ));
40
41
    return View::make('comments._form', compact('comment'));
42
  }
43
44
  /**

45
   * Store a newly created resource in storage.

46
   *

47
   * @return Response

48
   */
49
  public function store($post_id)
50
  {
51
    return $this->comments->store( $post_id, Input::all() );
52
  }
53
54
  /**

55
   * Display the specified resource.

56
   *

57
   * @param int $id

58
   * @return Response

59
   */
60
  public function show($post_id, $id)
61
  {
62
    return $this->comments->findById($post_id, $id);
63
  }
64
65
  /**

66
   * Show the form for editing the specified resource.

67
   *

68
   * @param int $id

69
   * @return Response

70
   */
71
  public function edit($post_id, $id)
72
  {
73
    $comment = $this->comments->findById($post_id, $id);
74
75
    return View::make('comments._form', compact('comment'));
76
  }
77
78
  /**

79
   * Update the specified resource in storage.

80
   *

81
   * @param int $id

82
   * @return Response

83
   */
84
  public function update($post_id, $id)
85
  {
86
    return $this->comments->update($post_id, $id, Input::all());
87
  }
88
89
  /**

90
   * Remove the specified resource from storage.

91
   *

92
   * @param int $id

93
   * @return Response

94
   */
95
  public function destroy($post_id, $id)
96
  {
97
    $this->comments->destroy($post_id, $id);
98
    return '';
99
  }
100
101
}

Tidak bisa jauh lebih sederhana daripada itu, Semua kontroler lakukan adalah membagi input data ke repositori, mengambil respon dari itu, dan menyerahkannya ke tampilan, yang dalam kasus kita adalah hanya JSON untuk sebagian besar metode kami. Ketika kami kembali fasih koleksi, atau Model fasih controller dalam Laravel 4, objek parsing ke JSON auto-ajaib, yang membuat pekerjaan kami sangat mudah.

Catatan: Perhatikan bahwa kita menambahkan beberapa lebih "menggunakan" pernyataan ke atas dari file untuk mendukung kelas-kelas lain yang kami menggunakan. Jangan lupa ini ketika Anda bekerja dalam sebuah namespace.

Satu-satunya hal yang agak sedikit rumit dalam controller ini adalah konstruktor. Perhatikan kita melewati dalam variabel diketik sebagai ketergantungan untuk Controller ini, namun ada gunanya bahwa kita memiliki akses ke Instansiasi controller ini untuk benar-benar memasukkan bahwa kelas... Selamat datang di ketergantungan injeksi! Apa yang kita benar-benar lakukan di sini adalah mengisyaratkan kami controller yang kita memiliki ketergantungan yang diperlukan untuk menjalankan kelas ini dan apa nama kelas (atau namanya mengikat IoC). Laravel menggunakan App::make() untuk membuat controller yang sebelum memanggil mereka. App::Make() akan mencoba untuk menyelesaikan item dengan mencari setiap binding yang kita mungkin telah dinyatakan, dan/atau menggunakan auto-loader untuk memberikan contoh. Selain itu, juga akan menyelesaikan semua dependensi yang diperlukan untuk instantiate kelas bagi kita, oleh lebih-atau-kurang secara rekursif panggilan App::make() pada masing-masing dari dependensi.

Jeli, akan melihat bahwa apa yang kita coba untuk lulus dalam ketergantungan adalah antarmuka, dan seperti yang Anda tahu, antarmuka tidak dapat instantiated. Ini adalah tempat hal keren dan kami benar-benar sudah melakukan hal yang sama dalam pengujian kami. Dalam pengujian kami Namun, kami menggunakan App::instance() untuk memberikan contoh sudah dibuat bukan antarmuka. Untuk controller kami, kami benar-benar akan memberitahu Laravel bahwa setiap kali sebuah instance dari PostRepositoryInterface diminta, untuk mengembalikan instance dari EloquentPostRepository.

Buka file app/routes.php Anda dan tambahkan baris berikut ke bagian atas dari file

1
App::bind('PostRepositoryInterface', 'EloquentPostRepository');
2
App::bind('CommentRepositoryInterface', 'EloquentCommentRepository');

Setelah menambahkan baris tersebut, Kapan saja meminta App::make() untuk contoh PostRepositoryInterface, itu akan menciptakan sebuah instance dari EloquentPostRepository, yang diduga untuk menerapkan PostRepositoryInterface. Jika Anda pernah berubah repositori Anda sebaliknya menggunakan ORM berbeda daripada fasih, atau mungkin berbasis file driver, Semua harus Anda lakukan adalah mengubah dua baris dan Anda baik untuk pergi, controller Anda akan tetap bekerja seperti biasa. Ketergantungan sebenarnya controller adalah objek yang mengimplementasikan antarmuka tersebut dan kita dapat menentukan pada saat run-time apa bahwa penerapan sebenarnya adalah.

PostRepositoryInterface dan CommentRepositoryInterface yang harus benar-benar ada dan binding harus benar-benar mengimplementasikannya. Jadi mari kita membuat mereka sekarang:

app/repositories/PostRepositoryInterface.php

1
<?php
2
3
interface PostRepositoryInterface {
4
  public function findById($id);
5
  public function findAll();
6
  public function paginate($limit = null);
7
  public function store($data);
8
  public function update($id, $data);
9
  public function destroy($id);
10
  public function validate($data);
11
  public function instance();
12
}

app/repositories/CommentRepositoryInterface.php

1
<?php
2
3
interface CommentRepositoryInterface {
4
  public function findById($post_id, $id);
5
  public function findAll($post_id);
6
  public function store($post_id, $data);
7
  public function update($post_id, $id, $data);
8
  public function destroy($post_id, $id);
9
  public function validate($data);
10
  public function instance();
11
}

Sekarang bahwa kita memiliki dua antarmuka kami dibangun, kita harus menyediakan implementasi antarmuka ini. Mari kita membangun mereka sekarang.

app/repositories/EloquentPostRepository.php

Sebagai implementasi ini namanya, kami mengandalkan fasih, yang kita dapat memanggil secara langsung. Jika Anda punya dependensi lain, ingat bahwa App::make() yang digunakan untuk menyelesaikan repositori ini, sehingga Anda dapat merasa bebas untuk menggunakan metode konstruktor yang sama kami digunakan dengan controller kami untuk menyuntikkan dependensi.

1
<?php
2
3
class EloquentPostRepository implements PostRepositoryInterface {
4
5
  public function findById($id)
6
  {
7
    $post = Post::with(array(
8
        'comments' => function($q)
9
        {
10
          $q->orderBy('created_at', 'desc');
11
        }
12
      ))
13
      ->where('id', $id)
14
      ->first();
15
16
    if(!$post) throw new NotFoundException('Post Not Found');
17
    return $post;
18
  }
19
20
  public function findAll()
21
  {
22
    return Post::with(array(
23
        'comments' => function($q)
24
        {
25
          $q->orderBy('created_at', 'desc');
26
        }
27
      ))
28
      ->orderBy('created_at', 'desc')
29
      ->get();
30
  }
31
32
  public function paginate($limit = null)
33
  {
34
    return Post::paginate($limit);
35
  }
36
37
  public function store($data)
38
  {
39
    $this->validate($data);
40
    return Post::create($data);
41
  }
42
43
  public function update($id, $data)
44
  {
45
    $post = $this->findById($id);
46
    $post->fill($data);
47
    $this->validate($post->toArray());
48
    $post->save();
49
    return $post;
50
  }
51
52
  public function destroy($id)
53
  {
54
    $post = $this->findById($id);
55
    $post->delete();
56
    return true;
57
  }
58
59
  public function validate($data)
60
  {
61
    $validator = Validator::make($data, Post::$rules);
62
    if($validator->fails()) throw new ValidationException($validator);
63
    return true;
64
  }
65
66
  public function instance($data = array())
67
  {
68
    return new Post($data);
69
  }
70
71
}

app/repositories/EloquentCommentRepository.php

1
<?php
2
3
class EloquentCommentRepository implements CommentRepositoryInterface {
4
5
  public function findById($post_id, $id)
6
  {
7
    $comment = Comment::find($id);
8
    if(!$comment || $comment->post_id != $post_id) throw new NotFoundException('Comment Not Found');
9
    return $comment;
10
  }
11
12
  public function findAll($post_id)
13
  {
14
    return Comment::where('post_id', $post_id)
15
      ->orderBy('created_at', 'desc')
16
      ->get();
17
  }
18
19
  public function store($post_id, $data)
20
  {
21
    $data['post_id'] = $post_id;
22
    $this->validate($data);
23
    return Comment::create($data);
24
  }
25
26
  public function update($post_id, $id, $data)
27
  {
28
    $comment = $this->findById($post_id, $id);
29
    $comment->fill($data);
30
    $this->validate($comment->toArray());
31
    $comment->save();
32
    return $comment;
33
  }
34
35
  public function destroy($post_id, $id)
36
  {
37
    $comment = $this->findById($post_id, $id);
38
    $comment->delete();
39
    return true;
40
  }
41
42
  public function validate($data)
43
  {
44
    $validator = Validator::make($data, Comment::$rules);
45
    if($validator->fails()) throw new ValidationException($validator);
46
    return true;
47
  }
48
49
  public function instance($data = array())
50
  {
51
    return new Comment($data);
52
  }
53
54
}

Jika Anda melihat di repositori kami, ada beberapa pengecualian yang kita membuang, mana tidak asli, atau apakah mereka milik Laravel. Mereka adalah pengecualian kustom yang kita gunakan untuk menyederhanakan kode kita. Dengan menggunakan pengecualian kustom, kami akan dapat dengan mudah menghentikan perkembangan aplikasi jika kondisi tertentu terpenuhi. Misalnya, jika posting tidak ditemukan, kita hanya dapat melemparkan NotFoundException dan aplikasi akan menangani hal itu sesuai, tetapi, bukan oleh menampilkan 500 error seperti biasa, sebaliknya kita akan penangan kesalahan kustom setup. Atau Anda bisa menggunakan App::abort(404) atau sesuatu di sepanjang baris tersebut, tapi saya menemukan bahwa metode ini menyelamatkan saya banyak pernyataan bersyarat dan ulangi kode, serta memungkinkan saya untuk menyesuaikan pelaksanaan kesalahan pelaporan dalam satu tempat sangat mudah.

Pertama mari kita menetapkan pengecualian kustom. Buat sebuah file dalam folder app disebut errors.php

1
touch app/errors.php

App/Errors.php

1
<?php
2
3
class PermissionException extends Exception {
4
5
  public function __construct($message = null, $code = 403)
6
  {
7
    parent::__construct($message ?: 'Action not allowed', $code);
8
  }
9
10
}
11
12
class ValidationException extends Exception {
13
14
  protected $messages;
15
16
  /**

17
   * We are adjusting this constructor to receive an instance

18
   * of the validator as opposed to a string to save us some typing

19
   * @param Validator $validator failed validator object

20
   */
21
  public function __construct($validator)
22
  {
23
    $this->messages = $validator->messages();
24
    parent::__construct($this->messages, 400);
25
  }
26
27
  public function getMessages()
28
  {
29
    return $this->messages;
30
  }
31
32
}
33
34
class NotFoundException extends Exception {
35
36
  public function __construct($message = null, $code = 404)
37
  {
38
    parent::__construct($message ?: 'Resource Not Found', $code);
39
  }
40
41
}

Ini adalah pengecualian yang sangat sederhana, perhatikan untuk ValidationException, kami hanya dapat melewati itu contoh validator gagal dan ini akan menangani pesan kesalahan yang sesuai!

Sekarang kita perlu menentukan penangan kesalahan kami yang akan dipanggil ketika salah satu pengecualian ini dilemparkan. Pada dasarnya ini adalah pendengar acara, setiap kali salah satu pengecualian ini dibuang, itu dianggap sebagai suatu peristiwa dan memanggil fungsi sesuai. Hal ini sangat sederhana untuk menambahkan penebangan atau penanganan prosedur berikut kesalahan lain.

App/Filters.php

1
...
2
3
/**

4
 * General HttpException handler

5
 */
6
App::error( function(Symfony\Component\HttpKernel\Exception\HttpException $e, $code)
7
{
8
  $headers = $e->getHeaders();
9
10
  switch($code)
11
  {
12
    case 401:
13
      $default_message = 'Invalid API key';
14
      $headers['WWW-Authenticate'] = 'Basic realm="CRM REST API"';
15
    break;
16
17
    case 403:
18
      $default_message = 'Insufficient privileges to perform this action';
19
    break;
20
21
    case 404:
22
      $default_message = 'The requested resource was not found';
23
    break;
24
25
    default:
26
      $default_message = 'An error was encountered';
27
  }
28
29
  return Response::json(array(
30
    'error' => $e->getMessage() ?: $default_message
31
  ), $code, $headers);
32
});
33
34
/**

35
 * Permission Exception Handler

36
 */
37
App::error(function(PermissionException $e, $code)
38
{
39
  return Response::json($e->getMessage(), $e->getCode());
40
});
41
42
/**

43
 * Validation Exception Handler

44
 */
45
App::error(function(ValidationException $e, $code)
46
{
47
  return Response::json($e->getMessages(), $code);
48
});
49
50
/**

51
 * Not Found Exception Handler

52
 */
53
App::error(function(NotFoundException $e)
54
{
55
  return Response::json($e->getMessage(), $e->getCode());
56
});

Kita sekarang perlu untuk membiarkan kami auto-loader tahu tentang file baru ini. Jadi kita harus memberitahu komposer mana untuk memeriksa mereka:

Composer.JSON

Perhatikan bahwa kita menambahkan baris "app/errors.php".

1
{
2
  "require": {
3
    "laravel/framework": "4.0.*",
4
    "way/generators": "dev-master",
5
    "twitter/bootstrap": "dev-master",
6
    "conarwelsh/mustache-l4": "dev-master"
7
  },
8
  "require-dev": {
9
    "phpunit/phpunit": "3.7.*",
10
    "mockery/mockery": "0.7.*"
11
  },
12
  "autoload": {
13
    "classmap": [
14
      "app/commands",
15
      "app/controllers",
16
      "app/models",
17
      "app/database/migrations",
18
      "app/database/seeds",
19
      "app/tests/TestCase.php",
20
      "app/repositories",
21
      "app/errors.php"
22
    ]
23
  },
24
  "scripts": {
25
    "post-update-cmd": "php artisan optimize"
26
  },
27
  "minimum-stability": "dev"
28
}

Kita sekarang harus memberitahu komposer untuk benar-benar memeriksa file ini dan memasukkan mereka dalam registri auto-beban.

1
composer dump-autoload

Besar, begitu kami telah menyelesaikan controller kami dan kami repositori, dua item dalam MVRC kita bahwa kita harus mengurus adalah model dan pemandangan, yang keduanya cukup lurus ke depan.

app/models/Post.php

1
<?php
2
/**

3
 * Represent a Post Item, or Collection

4
 */
5
class Post extends Eloquent {
6
7
  /**

8
   * Items that are "fillable"

9
   * meaning we can mass-assign them from the constructor

10
   * or $post->fill()

11
   * @var array

12
   */
13
  protected $fillable = array(
14
    'title', 'content', 'author_name'
15
  );
16
17
  /**

18
   * Validation Rules

19
   * this is just a place for us to store these, you could

20
   * alternatively place them in your repository

21
   * @var array

22
   */
23
  public static $rules = array(
24
    'title'    => 'required',
25
    'author_name' => 'required'
26
  );
27
28
  /**

29
   * Define the relationship with the comments table

30
   * @return Collection collection of Comment Models

31
   */
32
  public function comments()
33
  {
34
    return $this->hasMany('Comment');
35
  }
36
37
}

app/models/Comment.php

1
<?php
2
/**

3
 * Represent a Comment Item, or Collection

4
 */
5
class Comment extends Eloquent {
6
7
  /**

8
   * Items that are "fillable"

9
   * meaning we can mass-assign them from the constructor

10
   * or $comment->fill()

11
   * @var array

12
   */
13
  protected $fillable = array(
14
    'post_id', 'content', 'author_name'
15
  );
16
17
  /**

18
   * Validation Rules

19
   * this is just a place for us to store these, you could

20
   * alternatively place them in your repository

21
   * @var array

22
   */
23
  public static $rules = array(
24
    'post_id'   => 'required|numeric',
25
    'content'   => 'required',
26
    'author_name' => 'required'
27
  );
28
29
  /**

30
   * Define the relationship with the posts table

31
   * @return Model parent Post model

32
   */
33
  public function post()
34
  {
35
    return $this->belongsTo('Post');
36
  }
37
38
}

Sejauh menyangkut dilihat, aku hanya akan untuk menandai beberapa halaman bootstrap ramah sederhana. Ingatlah untuk mengubah setiap file ekstensi untuk .mustache meskipun, karena generator kami berpikir bahwa kami akan menggunakan. blade.php. Kami juga akan membuat beberapa pemandangan "sebagian" menggunakan konvensi Rails awalan mereka dengan _ menandakan parsial.

Catatan: saya melewatkan beberapa pemandangan, kami tidak akan menggunakan mereka dalam tutorial ini.

Public/views/Posts/index.mustache

Tampilan halaman indeks kami akan hanya loop melalui semua posting kami, menampilkan posting parsial untuk masing-masing.

1
{{#posts}}
2
  {{> posts._post}}
3
{{/posts}}

Public/views/Posts/Show.mustache

Tampilkan tampilan kami akan menunjukkan seluruh posting dan komentar:

1
<article>
2
  <h3>
3
    {{ post.title }} {{ post.id }}
4
    <small>{{ post.author_name }}</small>
5
  </h3>
6
  <div>
7
    {{ post.content }}
8
  </div>
9
</article>
10
11
<div>
12
  <h2>Add A Comment</h2>
13
  {{> comments._form }}
14
15
  <section data-role="comments">
16
    {{#post.comments}}
17
      <div>
18
        {{> comments._comment }}
19
      </div>
20
    {{/post.comments}}
21
  </section>
22
</div>

Public/views/Posts/_POST.mustache

Berikut ini adalah sebagian yang kita akan gunakan untuk menampilkan posting dalam daftar. Ini digunakan pada tampilan indeks kami.

1
<article data-toggle="view" data-target="posts/{{ id }}">
2
  <h3>{{ title }} {{ id }}</h3>
3
  <cite>{{ author_name }} on {{ created_at }}</cite>
4
</article>

Public/views/Posts/_form.mustache

Inilah bentuk parsial diperlukan untuk membuat posting, kita akan menggunakan ini dari API kami, tetapi ini juga bisa menjadi pandangan yang berguna dalam admin panel dan tempat lain, itulah sebabnya mengapa kita memilih untuk membuatnya parsial.

1
{{#exists}}
2
  <form action="/v1/posts/{{ post.id }}" method="post">
3
    <input type="hidden" name="_method" value="PUT" />
4
{{/exists}}
5
{{^exists}}
6
  <form action="/v1/posts" method="post">
7
{{/exists}}
8
9
  <fieldset>
10
11
    <div class="control-group">
12
      <label class="control-label"></label>
13
      <div class="controls">
14
        <input type="text" name="title" value="{{ post.title }}" />
15
      </div>
16
    </div>
17
18
    <div class="control-group">
19
      <label class="control-label"></label>
20
      <div class="controls">
21
        <input type="text" name="author_name" value="{{ post.author_name }}" />
22
      </div>
23
    </div>
24
25
    <div class="control-group">
26
      <label class="control-label"></label>
27
      <div class="controls">
28
        <textarea name="content">{{ post.content }}"</textarea>
29
      </div>
30
    </div>
31
32
    <div class="form-actions">
33
      <input type="submit" class="btn btn-primary" value="Save" />
34
    </div>
35
36
  </fieldset>
37
</form>

Public/views/Comments/_comment.mustache

Berikut adalah komentar parsial yang digunakan untuk mewakili satu komentar dalam daftar komentar:

1
<h5>
2
  {{ author_name }}
3
  <small>{{ created_at }}</small>
4
</h5>
5
<div>
6
  {{ content }}
7
</div>

Public/views/Comments/_form.mustache

Formulir yang diperlukan untuk membuat komentar - keduanya digunakan dalam API dan pemandangan Tampilkan Post:

1
{{#exists}}
2
  <form class="form-horizontal" action="/v1/posts/{{ comment.post_id }}/{{ id }}" method="post">
3
    <input type="hidden" name="_method" value="PUT" />
4
{{/exists}}
5
{{^exists}}
6
  <form class="form-horizontal" action="/v1/posts/{{ comment.post_id }}" method="post">
7
{{/exists}}
8
9
  <fieldset>
10
11
    <div class="control-group">
12
      <label class="control-label">Author Name</label>
13
      <div class="controls">
14
        <input type="text" name="author_name" value="{{ comment.author_name }}" />
15
      </div>
16
    </div>
17
18
    <div class="control-group">
19
      <label class="control-label">Comment</label>
20
      <div class="controls">
21
        <textarea name="content">{{ comment.content }}</textarea>
22
      </div>
23
    </div>
24
25
    <div class="form-actions">
26
      <input type="submit" class="btn btn-primary" value="Save" />
27
    </div>
28
29
  </fieldset>
30
</form>

Public/views/layouts/_notification.mustache

Dan di sini adalah pandangan penolong parsial untuk memungkinkan kita untuk menunjukkan pemberitahuan:

1
<div class="alert alert-{{type}}">
2
  {{message}}
3
</div>

Besar, kita memiliki semua komponen API kami di tempat. Mari kita jalankan unit kami tes untuk melihat mana kita berada di!

1
vendor/phpunit/phpunit/phpunit.php

Pertama menjalankan tes ini harus lulus dengan terbang warna (hijau). Namun, jika Anda adalah untuk menjalankan tes ini lagi, Anda akan melihat bahwa gagal sekarang dengan beberapa kesalahan, dan itu adalah karena kami tes repositori benar-benar diuji database, dan dengan demikian dihapus beberapa catatan pengujian kami sebelumnya digunakan untuk menegaskan nilai-nilai. Ini adalah memperbaiki yang mudah, Semua harus kita lakukan adalah memberitahu kami tes bahwa mereka perlu kembali benih database setelah setiap tes. Selain itu, kami tidak menerima kesalahan yang terlihat untuk ini, tapi kami pun tidak menutup ejekan setelah setiap tes yang baik, ini merupakan syarat ejekan yang dapat Anda temukan di Documents mereka. Jadi mari kita menambahkan kedua metode yang hilang.

Buka app/tests/TestCase.php dan menambahkan dua metode berikut:

1
/**

2
 * setUp is called prior to each test

3
 */
4
public function setUp()
5
{
6
  parent::setUp();
7
  $this->seed();
8
}
9
10
/**

11
 * tearDown is called after each test

12
 * @return [type] [description]

13
 */
14
public function tearDown()
15
{
16
  Mockery::close();
17
}

Ini sangat bagus, kita sekarang mengatakan bahwa di setiap "setUp", yang dijalankan sebelum setiap tes, untuk kembali benih database. Namun kita masih memiliki satu masalah, setiap kali Anda kembali benih, itu hanya akan untuk menambahkan baris baru ke dalam tabel. Pengujian kami mencari item dengan ID baris satu, jadi kita masih memiliki beberapa perubahan untuk membuat. Kita hanya perlu memberitahu database untuk memotong meja kami ketika penyemaian:

app/database/seeds/CommentsTableSeeder.php

Sebelum kita menyisipkan baris baru, kita akan memotong Meja, menghapus semua baris dan reset auto-increment counter.

1
<?php
2
3
class CommentsTableSeeder extends Seeder {
4
5
  public function run()
6
  {
7
    $comments = array(
8
      array(
9
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
10
        'author_name' => 'Testy McTesterson',
11
        'post_id'   => 1,
12
        'created_at' => date('Y-m-d H:i:s'),
13
        'updated_at' => date('Y-m-d H:i:s'),
14
      ),
15
      array(
16
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
17
        'author_name' => 'Testy McTesterson',
18
        'post_id'   => 1,
19
        'created_at' => date('Y-m-d H:i:s'),
20
        'updated_at' => date('Y-m-d H:i:s'),
21
      ),
22
      array(
23
        'content'   => 'Lorem ipsum Nisi dolore ut incididunt mollit tempor proident eu velit cillum dolore sed',
24
        'author_name' => 'Testy McTesterson',
25
        'post_id'   => 2,
26
        'created_at' => date('Y-m-d H:i:s'),
27
        'updated_at' => date('Y-m-d H:i:s'),
28
      ),
29
    );
30
31
    //truncate the comments table when we seed

32
    DB::table('comments')->truncate();
33
    DB::table('comments')->insert($comments);
34
  }
35
36
}

app/database/seeds/PostsTableSeeder.php

1
<?php
2
3
class PostsTableSeeder extends Seeder {
4
5
  public function run()
6
  {
7
    $posts = array(
8
      array(
9
        'title'    => 'Test Post',
10
        'content'   => 'Lorem ipsum Reprehenderit velit est irure in enim in magna aute occaecat qui velit ad.',
11
        'author_name' => 'Conar Welsh',
12
        'created_at' => date('Y-m-d H:i:s'),
13
        'updated_at' => date('Y-m-d H:i:s'),
14
      ),
15
      array(
16
        'title'    => 'Another Test Post',
17
        'content'   => 'Lorem ipsum Reprehenderit velit est irure in enim in magna aute occaecat qui velit ad.',
18
        'author_name' => 'Conar Welsh',
19
        'created_at' => date('Y-m-d H:i:s'),
20
        'updated_at' => date('Y-m-d H:i:s'),
21
      )
22
    );
23
24
    //truncate the posts table each time we seed

25
    DB::table('posts')->truncate();
26
    DB::table('posts')->insert($posts);
27
  }
28
29
}

Sekarang Anda harus mampu menjalankan tes beberapa kali dan mendapatkan melewati tes setiap kali! Itu berarti kita telah memenuhi siklus TDD kami dan kami tidak diizinkan untuk menulis lagi kode produksi untuk API kami!! Mari kita hanya melakukan perubahan kami repo kami dan pindah ke aplikasi Backbone!

1
git add . && git commit -am "built out the API and corresponding tests"

Tulang punggung App

Sekarang bahwa kita telah menyelesaikan semua pekerjaan pendukungnya, kita dapat bergerak maju untuk menciptakan bagus user interface untuk mengakses semua data itu. Kami akan membuat ini bagian dari proyek sedikit di sisi yang lebih sederhana, dan saya memperingatkan Anda bahwa pendekatan saya dapat dianggap satu dogmatis. Saya telah melihat banyak orang dengan begitu banyak metode yang berbeda untuk penataan aplikasi tulang punggung. Cobaan dan kesalahan saya telah membawa saya untuk metode saya saat ini, jika Anda tidak setuju dengan hal itu, harapan saya adalah bahwa itu dapat menginspirasi Anda untuk menemukan Anda sendiri!

Kita akan menggunakan mesin template kumis bukan Underscore, ini akan memungkinkan kita untuk berbagi pandangan kami antara klien dan server! Kuncinya adalah dalam bagaimana Anda memuat pandangan, kita akan menggunakan AJAX dalam tutorial ini, tetapi hanya sebagai mudah untuk memuat mereka semua ke dalam template utama, atau precompile mereka.

Router

Pertama kita akan mendapatkan kami akan router. Ada dua bagian ini, Laravel router, dan router tulang punggung.

Laravel Router

Ada dua pendekatan utama yang dapat kita ambil di sini:

Pendekatan #1: Catch-semua

Ingat saya katakan ketika Anda telah menambahkan jalur sumber daya itu penting bahwa Anda menempatkan mereka di atas jalur app?? Metode menangkap-semua adalah alasan untuk pernyataan itu. Tujuan keseluruhan dari metode ini adalah memiliki setiap rute yang memiliki tidak menemukan kecocokan di Laravel, tertangkap dan dikirim ke tulang punggung. Menerapkan metode ini mudah:

App/Routes.php

1
// change your existing app route to this:

2
// we are basically just giving it an optional parameter of "anything"

3
Route::get('/{path?}', function($path = null)
4
{
5
  return View::make('app');
6
})
7
->where('path', '.*'); //regex to match anything (dots, slashes, letters, numbers, etc)

Sekarang, setiap rute selain rute API kami akan membuat pandangan app kami.

Selain itu, jika Anda memiliki multi-halaman app (beberapa satu halaman aplikasi), Anda dapat menetapkan beberapa ini menangkap-alls:

1
Route::get('someApp1{path?}', function($path = null)
2
{
3
  return View::make('app');
4
})
5
->where('path', '.*');
6
7
Route::get('anotherApp/{path?}', function($path = null)
8
{
9
  return View::make('app');
10
})
11
->where('path', '.*');
12
13
Route::get('athirdapp{path?}', function($path = null)
14
{
15
  return View::make('app');
16
})
17
->where('path', '.*');

Catatan: Perlu diingat '/' sebelum {jalan?}. Jika garis miring yang ada, itu akan diminta dalam URL (dengan pengecualian rute indeks), kadang-kadang ini diinginkan dan kadang-kadang tidak.

Pendekatan #2:

Karena kami depan dan belakang berbagi pandangan... Bukankah akan sangat mudah untuk hanya menentukan rute di kedua tempat? Anda bahkan dapat melakukannya selain pendekatan menangkap-semua jika Anda ingin.

Rute yang kita akan berakhir dengan mendefinisikan untuk app adalah hanya:

1
GET /
2
GET /posts/:id

App/Routes.php

1
<?php
2
3
App::bind('PostRepositoryInterface', 'EloquentPostRepository'); 
4
App::bind('CommentRepositoryInterface', 'EloquentCommentRepository'); 
5
6
7
8
9
10
//create a group of routes that will belong to APIv1

11
Route::group(array('prefix' => 'v1'), function()
12
{
13
  Route::resource('posts', 'V1\PostsController');
14
  Route::resource('posts.comments', 'V1\PostsCommentsController');
15
});
16
17
18
19
/**

20
 * Method #1: use catch-all

21
 * optionally commented out while we use Method 2

22
 */
23
// change your existing app route to this:

24
// we are basically just giving it an optional parameter of "anything"

25
// Route::get('/{path?}', function($path = null)

26
// {

27
//   return View::make('layouts.application')->nest('content', 'app');

28
// })

29
// ->where('path', '.*'); //regex to match anything (dots, slashes, letters, numbers, etc)

30
31
32
33
/**

34
 * Method #2: define each route

35
 */
36
Route::get('/', function()
37
{
38
  $posts = App::make('PostRepositoryInterface')->paginate();
39
  return View::make('layouts.application')->nest('content', 'posts.index', array(
40
    'posts' => $posts
41
  ));
42
});
43
44
Route::get('posts/{id}', function($id)
45
{
46
  $post = App::make('PostRepositoryInterface')->findById($id);
47
  return View::make('layouts.application')->nest('content', 'posts.show', array(
48
    'post' => $post
49
  ));
50
});

Cukup keren ya?! Terlepas dari metode yang kita gunakan, atau kombinasi keduanya, router tulang punggung Anda akan berakhir umumnya sama.

Perhatikan bahwa kita menggunakan repositori kami lagi, ini adalah alasan lain mengapa repositori adalah tambahan yang berguna untuk kerangka kami. Kita sekarang dapat menjalankan hampir semua logika yang controller, tetapi tanpa mengulangi hampir tidak ada kode!

Perlu diingat beberapa hal saat memilih metode yang digunakan, jika Anda menggunakan menangkap-semua, akan melakukan seperti namanya... catch-semua. Ini berarti tidak ada hal seperti 404 situs Anda lagi. Tidak peduli atas permintaan, yang mendarat di halaman app (kecuali jika Anda secara manual melemparkan pengecualian di suatu tempat seperti repositori Anda). Invers adalah, dengan mendefinisikan setiap rute, sekarang Anda memiliki dua set rute untuk mengelola. Kedua metode memiliki mereka pasang dan surut, tetapi keduanya sama mudah untuk menangani.

Pandangan dasar

Satu pandangan untuk memerintah mereka semua! BaseView ini adalah pandangan bahwa semua pandangan-pandangan kami akan mewarisi dari. Untuk tujuan kita, pandangan ini memiliki tetapi satu pekerjaan... template! Dalam aplikasi yang lebih besar pandangan ini adalah tempat yang baik untuk menempatkan logika lain bersama.

Kami hanya akan memperpanjang Backbone.View dan tambahkan fungsi template yang akan kembali pandangan kita dari cache jika ada, atau mendapatkannya melalui AJAX dan menempatkannya dalam cache. Kita harus menggunakan AJAX sinkron karena cara bahwa Mustache.js mengambil parsial, tetapi karena kita hanya mengambil pandangan ini jika mereka tidak di-cache, kita tidak boleh menerima banyak pertunjukan hit sini.

1
/**

2
 ***************************************

3
 * Array Storage Driver

4
 * used to store our views

5
 ***************************************

6
 */
7
var ArrayStorage = function(){
8
  this.storage = {};
9
};
10
ArrayStorage.prototype.get = function(key)
11
{
12
  return this.storage[key];
13
};
14
ArrayStorage.prototype.set = function(key, val)
15
{
16
  return this.storage[key] = val;
17
};
18
19
20
21
/**

22
 ***************************************

23
 * Base View

24
 ***************************************

25
 */
26
var BaseView = bb.View.extend({
27
28
  /**

29
   * Set our storage driver

30
   */
31
  templateDriver: new ArrayStorage,
32
33
  /**

34
   * Set the base path for where our views are located

35
   */
36
  viewPath: '/views/',
37
38
  /**

39
   * Get the template, and apply the variables

40
   */
41
  template: function()
42
  {
43
    var view, data, template, self;
44
45
    switch(arguments.length)
46
    {
47
      case 1:
48
        view = this.view;
49
        data = arguments[0];
50
        break;
51
      case 2:
52
        view = arguments[0];
53
        data = arguments[1];
54
        break;
55
    }
56
57
    template = this.getTemplate(view, false);
58
    self = this;
59
60
    return template(data, function(partial)
61
    {
62
      return self.getTemplate(partial, true);
63
    });
64
  },
65
66
  /**

67
   * Facade that will help us abstract our storage engine,

68
   * should we ever want to swap to something like LocalStorage

69
   */
70
  getTemplate: function(view, isPartial)
71
  {
72
    return this.templateDriver.get(view) || this.fetch(view, isPartial);
73
  },
74
75
  /**

76
   * Facade that will help us abstract our storage engine,

77
   * should we ever want to swap to something like LocalStorage

78
   */
79
  setTemplate: function(name, template)
80
  {
81
    return this.templateDriver.