Laravel 5 ile CMS - Controller, Model, Request, Provider, Form

Bu makalede temel olarak Controller, Model ve Requestleri inceleyip, Provider ve Form kullanımını daha detaylı inceleyeceğiz. Bu yazının sonunda yönetici panelinin büyük bir bölümü bitmiş olacak. Yazıyı çok uzatmamak için, yaptığım tercümeleri veya stil değişikliklerinden bahsetmeyeceğim, Github'tan bunları elde edebilirsiniz ki zaten serinin sonunda hızlı bir şekilde kurulumunu yapabileceğiniz, her açıdan fonksiyonel olan bu içerik yönetim sistemini kullanıp, kendi ihtiyaçlarınıza göre değiştirebilirsiniz, önemli olan neyin ne olduğunu bilebilmeniz.

Admin layoutunu her şeyiyle oluşturmadan önce admin kısmında kullanacağımız controller, model ve requestleri oluşturalım. Requestler validation yapmamıza yarayan Laravel modülü, birazdan daha detaylı inceleyeceğiz. Bir de burada belirtmem gereken bir şey daha var, php artisan ile birçok kısmı hızlı bir şekilde gerçekleştirebiliyoruz, ama eksik olan birkaç kısım var, bunlar için de bir paket var, kullanıp kullanmamak size kalmış ama ben kullanmayı tercih ediyorum, artisan'ın daha da fonksiyonel hale gelmesi bizim için daha iyi.
sudo composer require laracasts/generators --dev
Sonrasında da sadece geliştirme ortamında kullanmak için, app/Providers/AppServiceProvider.php içerisinde:
public function register()
{
if ($this->app->environment() == 'local')
{
$this->app->register('Laracasts\Generators\GeneratorsServiceProvider');
}
}
Son olarak da terminalde composer dump yazmayı unutmayın. Sırasıyla admin panelimizde bizim için lazım olan her şeyi oluşturmak için:
php artisan make:controller Admin/UserController
php artisan make:migration:schema create_languages_table --schema="title:string, flag:string, code:string:unique,site_title:string, site_description:string"
php artisan make:controller Admin/LanguageController
php artisan make:request LanguageRequest
php artisan make:migration:schema create_categories_table --schema="language_id:unsignedInteger:foreign, title:string, slug:string:unique, description:string, color:string"
php artisan make:controller Admin/CategoryController
php artisan make:request CategoryRequest
php artisan make:migration:schema create_articles_table --schema="category_id:unsignedInteger:foreign, title:string, slug:string:unique, content:text, published_at:date, description:string, read:integer:default(0)"
php artisan make:controller Admin/ArticleController
php artisan make:request ArticleRequest
php artisan make:controller Admin/PageController
php artisan make:migration:schema create_pages_table --schema="language_id:unsignedInteger:foreign, parent_id:integer:nullable, lft:integer:nullable, rgt:integer:nullable, depth:integer:nullable, title:string, slug:string:unique, content:text, description:string, read:integer:default(0)"
php artisan make:request PageRequest
php artisan make:controller --plain Admin/SettingController
php artisan make:migration:schema create_settings_table --schema="logo:string, email:string, facebook:string, twitter:string, status:tinyInteger"
php artisan make:request SettingRequest
Bu işlemleri elle yapmaktansa bu şekilde yapmak gördüğünüz gibi gayet pratik, sonuçlarını yazmayacağım ancak migration dosyalarını açarsanız, foreign keylerde tablo referanslarının da olduğunu göreceksiniz. Yalnız foreign key referansı verirken, o alanın unsigned olması lazım, dikkat edin, çünkü increment otomatik olarak unsigned integer(10) alanı oluşturuyor. Dolayısıyla o alanın, referans ettiğiniz alan ile birebir aynı özelliklerde olması lazım. Bunun yanında oluşturacağımız sistemin çoklu dil özelliği olacak yukarıda da görebileceğiniz gibi. Tablo yapısı biraz farklı olan bir tek sayfalar var, onun sebebi de sayfaları nested(Türkçe karşılığı iç içe ama bunu nested olarak bilmeniz daha iyi) oluşturacak olmamız, yani her bir sayfanın ait olduğu bir parentı yani üst bireyi ya da çocukları olabilir, tüm bunları da üstte oluşturduğumuz tablo yapısıyla sağlayabileceğiz, o kısma gelince neden üstteki yapıyı kurduğumuzu daha iyi anlayacaksınız, hatta daha önceden herhangi bir programlama dilinde ağaçlarla uğraştıysanız çoktan anlamış olmanız lazım. Bu arada model, request ve controllerların içini doldururuken, yine route düzenlemesi yapıp, view dosyalarını da oluşturmamız lazım.

Öncelikle, tüm bu kısımlarda işimizi daha da kolaylaştıracak birkaç eklenti daha kuracağız. Sayfaların nested yapıya sahip olabilmesi için, composer.json dosyanızın require kısmına "baum/baum": "~1.1", datatable oluştururken, işlemleri sunucu tarafında halledebilmek için "chumper/datatable": "dev-develop", formları daha düzenli oluşturmak ve view katmanından ayırmak için "kris/laravel-form-builder": "~1.4", kullanıcılara daha rahat ve güzel bildirimler verebilmek için "laracasts/flash": "~1.3", menüler için "caffeinated/menus": "[email protected]", XSS filtrelemek için "mews/purifier": "dev-master" satırlarını ekledikten sonra sudo composer update yazın, ardından config/app.php dosyasının providers dizisine bunları ekleyin: 'Baum\Providers\BaumServiceProvider', 'Chumper\Datatable\DatatableServiceProvider', 'Kris\LaravelFormBuilder\FormBuilderServiceProvider', 'Laracasts\Flash\FlashServiceProvider', 'Caffeinated\Menus\MenusServiceProvider', 'Mews\Purifier\PurifierServiceProvider' satırlarını ekleyin, aliases kısmına da 'Datatable' => 'Chumper\Datatable\Facades\DatatableFacade', 'FormBuilder' => 'Kris\LaravelFormBuilder\Facades\FormBuilder', 'Flash' => 'Laracasts\Flash\Flash', 'Menu' => 'Caffeinated\Menus\Facades\Menu', 'Purifier' => 'Mews\Purifier\Facades\Purifier' satırlarını ekleyin. Her bir model için de formlarımızı, menüleri yönetebilmek için de bir provider oluşturmamız lazım, içeriklerini de sırasıyla dolduracağız:
php artisan make:form Forms/LanguagesForm
php artisan make:form Forms/ArticlesForm
php artisan make:form Forms/CategoriesForm
php artisan make:form Forms/SettingsForm
php artisan make:form Forms/PagesForm
php artisan make:form Forms/UsersForm
php artisan make:provider MenuServiceProvider
Burada hemen Providerların ne olduğundan tekrar bahsedeyim, bir önceki makalede anlatmıştım ama şimdi tekrar sürekli provider ekledik, o yüzden kafanız karışmış olabilir ve yine bir tane daha eklememiz lazım, nereye eklemeniz gerektiğini tahmin etmeniz lazım, edemiyorsanız, yine app/config.php içerisinde providers dizisine 'App\Providers\MenuServiceProvider' satırını eklemelisiniz. Providerlar, uygulamada spesifik bir olayla ya da herhangi bir anda uygulamanın belirli kısımlara ulaşabilmesini sağlayan araçlardır. Biz yeni bir şey oluşturduğumuzda sürekli bunu provider dizisine bu amaçla ekliyoruz, bir nevi onları Laravel'e tanıtıyoruz. Menülerimize de bu şekilde uygulamanın herhangi bir kısmında ulaşabileceğiz.

Tekrardan app/Http/routes.php dosyamızı düzenleyelim ve yukarıda oluşturduğumuz yeni parçaları, daha doğrusu resourceları routelar arasına dahil edelim.
Route::group(['prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'auth'], function()
{
Route::get('/', ['as' => 'admin.root', 'uses' => '[email protected]']);
Route::resource('user', 'UserController');
Route::resource('article', 'ArticleController');
Route::resource('category', 'CategoryController');
Route::resource('language', 'LanguageController');
Route::resource('page', 'PageController');
Route::get('/settings', ['as' => 'admin.settings', 'uses' => '[email protected]']);
});
Bu kısmı bir önceki makaleden hatırlamanız lazım, geçen sefer namespace referansı vermemiştik, ama namespace referansını verince örneğin Admin içerisinde yer alan UserController'ı belirtmemiz için her seferinde başına Admin yazmamız gerekmiyor. Azıcık da olsa REST'in ne olduğuna dair fikriniz olduğunu düşündüğümden, resource'un REST routelarını bizim için oluşturduğunu söyleyebilirim. Örneğin User için yaptığımız bu resource route'u alttaki çıktıyı üretiyor. Bu listeyi tamamıyla görmek için tekrar hatırlatmam gerekirse, php artisan route:list yazmanız lazım.
| Method        | URI                      | Name                  | Action
| GET|HEAD | admin/user | admin.user.index | App\Http\Controllers\Admin\[email protected]
| GET|HEAD | admin/user/create | admin.user.create | App\Http\Controllers\Admin\[email protected]
| POST | admin/user | admin.user.store | App\Http\Controllers\Admin\[email protected]
| GET|HEAD | admin/user/{user} | admin.user.show | App\Http\Controllers\Admin\[email protected]
| GET|HEAD | admin/user/{user}/edit | admin.user.edit | App\Http\Controllers\Admin\[email protected]
| PUT | admin/user/{user} | admin.user.update | App\Http\Controllers\Admin\[email protected]
| PATCH | admin/user/{user} | | App\Http\Controllers\Admin\[email protected]
| DELETE | admin/user/{user} | admin.user.destroy | App\Http\Controllers\Admin\[email protected]
app/Providers/MenuServiceProvider.php dosyamızın içeriği de alttaki gibi olacak.

use Menu;
use Illuminate\Support\ServiceProvider;

class MenuServiceProvider extends ServiceProvider {

public function boot()
{
Menu::make('admin', function($menu) {
$dashboard = $menu->add(trans('admin.menu.dashboard'), ['route' => 'admin.root'])
->icon('dashboard');

$language = $menu->add(trans('admin.menu.language.root'), '#')
->icon('flag');
$language->add(trans('admin.menu.language.add'), ['route' => 'admin.language.create'])
->icon('circle-o');
$language->add(trans('admin.menu.language.all'), ['route' => 'admin.language.index'])
->icon('circle-o');

$pages = $menu->add(trans('admin.menu.page.root'), '#')
->icon('folder');
$pages->add(trans('admin.menu.page.add'), ['route' => 'admin.page.create'])
->icon('circle-o');
$pages->add(trans('admin.menu.page.all'), ['route' => 'admin.page.index'])
->icon('circle-o');

$categories = $menu->add(trans('admin.menu.category.root'), '#')
->icon('book');
$categories->add(trans('admin.menu.category.add'), ['route' => 'admin.category.create'])
->icon('circle-o');
$categories->add(trans('admin.menu.category.all'), ['route' => 'admin.category.index'])
->icon('circle-o');

$articles = $menu->add(trans('admin.menu.article.root'), '#')
->icon('edit');
$articles->add(trans('admin.menu.article.add'), ['route' => 'admin.article.create'])
->icon('circle-o');
$articles->add(trans('admin.menu.article.all'), ['route' => 'admin.article.index'])
->icon('circle-o');

$users = $menu->add(trans('admin.menu.user.root'), '#')
->icon('users');
$users->add(trans('admin.menu.user.add'), ['route' => 'admin.user.create'])
->icon('circle-o');
$users->add(trans('admin.menu.user.all'), ['route' => 'admin.user.index'])
->icon('circle-o');

$settings = $menu->add(trans('admin.menu.settings'), ['route' => 'admin.settings'])
->icon('gears');
});
}

public function register()
{

}

}
Artık admin layoutuna geri dönebiliriz. resources/views/layouts/admin.blade.php dosyanın son hali alttaki gibi olacak. flash:message kısmı nereden geliyor diye düşünecek olursanız da, en başta belirttiğim Flash mesaj modülünden geliyor, kullanımını birazdan göreceksiniz. 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title> {{  trans(Route::getCurrentRoute()->getName()) . ' | ' .  trans('admin.title')  }} </title>
    <link rel="stylesheet" type="text/css" href="{{ url( elixir('css/admin.css') ) }}">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    <script src="{{ url( elixir('js/admin.js') ) }}" type="text/javascript"></script>
    <script src="{{ url( 'js/admin-custom.js' ) }}" type="text/javascript"></script>
</head>
<body class="skin-blue">
<div class="wrapper">
    @include('partials.admin.top')
    @include('partials.admin.sidebar')
    <div class="content-wrapper">
        @include('partials.admin.header')
        <section class="content">
            <div class="row">
                <div class="col-xs-12">
                    <div class="box">
                        <div class="box-body">
                            @include('flash::message')
                            @yield('content')
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>
</div>
</body>
</html>
Gördüğünüz üzere iki tane partial var dahil etmediğimiz, bunlarda menümüzü çağıracağız ve uygulamanın üst kısmını düzenleyeceğiz.

resources/views/partials/admin/sidebar.blade.php dosyasının içeriği alttaki gibi. Üstte oluşturduğumuz menünün ismini admin koyduğumuzu gözden kaçırmayın, bu menüyü de $menu_admin şeklinde referans ediyoruz.
<ul class="sidebar-menu">
    @foreach($items as $item)
        <li @if($item->hasChildren())class ="treeview"@endif>
            <a href="{{ $item->url() }}">
                {!! $item->title !!}
                @if($item->hasChildren()) <i class="fa fa-angle-left pull-right"></i> @endif
            </a>
            @if($item->hasChildren())
                <ul class="treeview-menu">
                    @foreach($item->children() as $child)
                        <li><a href="{{ $child->url() }}">{!! $child->title !!}</a></li>
                    @endforeach
                </ul>
            @endif
        </li>
    @endforeach
</ul>
Dil dosyalarını tekrar tekrar yazmak istemediğimi belirtmiştim, o yüzden Github üzerinden onlara bakabilirsiniz, dil dosyaları ne işe yarıyordu diyorsanız, trans helperının dil dosyasındaki karşılığı aradığını tekrar hatırlatmam gerekir. Artık admin panelimizin belli bir iskeleti var, sonunda nasıl olacağını da alttaki resimden kestirebilirsiniz.
Laravel Admin Paneli

Son olarak, yeni bir provider daha yaratıp, herhangi bir şekilde sınıflarımızın içinde yer almasını istemediğimiz, ama işimize yarayacak Helper fonksiyonlarını barındıracağız kısmı da oluşturursak işimize yarayabilir.
php artisan make:provider HelperServiceProvider
app klasörü içerisinde de Helper klasörü oluşturup, yeni oluşturduğumuz Helper servisinin de içeriğini alttaki gibi oluşturalım ve tabii ki config/app.php providers kısmına 'App\Providers\HelperServiceProvider', satırını ekleyin. Helper klasörünün içerisinde AdminHelper.php diye bir dosya oluşturup içeriğini de alttaki gibi yaparsanız, datatable'ın içerisinde her bir kayıtın kendine has operasyonları için gerekli olan kısmı, aynı zamanda da breadcrumbs olarak tabir ettiğimiz, hangi sayfada olduğumuzu ya da üst sayfaları gösteren kısımları bize gösteren kısmı, datatable'ın olduğu her sayfada bize + işareti ile verdiği link ile, o anki kaynaktan yeni bir tane oluşturmamızı sağlayan linki ve yüklediğimiz resimlerin ismini düzenleyen yardımcı fonksiyonları ekleyeceğiz. Burada önemli olan kısım, tüm routing işlemlerinin bir isme sahip olması ve language dosyalarında da aynı referansla karşılıklarının olması.

<?php

if( ! function_exists('get_ops'))
{
    /**
     * Returns resource operations for the datatable
     *
     * @param $resource
     * @param $id
     * @return string
     */
    function get_ops($resource, $id)
    {
        $show_path = route('admin.'.$resource.'.show', ['id' => $id]);
        $edit_path = route('admin.'.$resource.'.edit', ['id' => $id]);
        $delete_path = route('admin.'.$resource.'.destroy', ['id' => $id]);
        $ops  = '<ul class="list-inline no-margin-bottom">';
        $ops .=  '<li>';
        $ops .=  '<a class="btn btn-xs bg-navy" href="'.$show_path.'"><i class="fa fa-search"></i> '.trans('admin.ops.show').'</a>';
        $ops .=  '</li>';
        $ops .=  '<li>';
        $ops .=  '<a class="btn btn-xs bg-olive" href="'.$edit_path.'"><i class="fa fa-pencil-square-o"></i> '.trans('admin.ops.edit').'</a>';
        $ops .=  '</li>';
        $ops .=  '<li>';
        $ops .= Form::open(['method' => 'DELETE', 'url' => $delete_path]);
        $ops .= Form::submit('&#xf1f8; ' .trans('admin.ops.delete'), ['onclick' => "return confirm('".trans('admin.ops.confirmation')."');", 'class'=>'btn btn-xs btn-danger destroy']);
        $ops .= Form::close();
        $ops .=  '</li>';
        $ops .=  '</ul>';
        return $ops;
    }
}


if ( ! function_exists('breadcrumbs'))
{
    /**
     * Return breadcrumbs for each resource methods
     *
     * @return string
     */
    function breadcrumbs()
    {
        $route = Route::currentRouteName();
        // get after last dot
        $index = substr($route, 0, strrpos($route, '.') + 1) . 'index';
        $breadcrumbs  = '<ol class="breadcrumb">';
        $breadcrumbs .= '<li><a href="'.route('admin.root').'"><i class="fa fa-dashboard"></i> '.trans('admin.menu.dashboard').'</a></li>';
        // if not admin root
        if(strpos($route, 'root')  === false)
        {
            $breadcrumbs  .= strpos($route, 'index')  !== false ? '<li class="active">' : '<li>';
            $parent_text   = strpos($route, 'index')  !== false ? trans($route) : trans($index);
            $breadcrumbs  .= strpos($route, 'index')  !== false ? $parent_text : '<a href="'.route($index).'">'.$parent_text.'</a>';
            $breadcrumbs  .= '</li>';
            if(strpos($route, 'index')  === false)
            {
                $breadcrumbs  .= '<li class="active">'.trans($route).'</li>';
            }
        }
        $breadcrumbs .= '</ol>';
        return $breadcrumbs;
    }
}

if ( ! function_exists('header_title'))
{
    /**
     * Return the header title for each page
     *
     * @return string
     */
    function header_title()
    {
        $route = Route::currentRouteName();
        $title = '<h1>';
        $title .= trans(Route::getCurrentRoute()->getName());
        if( strpos($route, 'index')  !== false )
        {
            $new = substr($route, 0, strrpos($route, '.') + 1) . 'create';
            $title .= '<small>';
            $title .= '<a href="'.route($new).'" title="'.trans($new).'">';
            $title .= '<i class="fa fa-plus"></i>';
            $title .= '</a>';
            $title .= '</small>';
        }
        $title .= '</h1>';
        return $title;
    }
}

if ( ! function_exists('rename_file'))
{
    /**
     * Rename the filename, convert string to url friendly form and attach random string
     *
     * @param $filename
     * @param $mime
     * @return string
     */
    function rename_file($filename, $mime)
    {
        // remove extension first
        $filename = preg_replace('/\\.[^.\\s]{3,4}$/', '', $filename);
        $filename = str_slug($filename, "-");
        $filename = '/' . $filename . '_' . str_random(32) .  '.' . $mime;
        return $filename;
    }
}
Bu helper fonksiyonlarında url'leri oluştururken, route isimlerinden faydalandığımızı unutmayın. Bir de kaynak operasyonları için, HTTP/DELETE isteğini Laravel'de kullanabiliyoruz, o yüzden DELETE yerine GET kullanmak gereksiz olduğu için, onu da o şekile yapıyoruz. Gördüğünüz entity() de font awesome'un çöp kutusu entitiysi, onu da o şekilde ekleyebiliyorsunuz, tabii bir danger classı için less dosyanızda font-family: FontAwesome yazmanız lazım, ancak çok detaya girmeyeceğim, dosyalardan inceleyebilirsiniz. Son olarak da resources/views/partials/admin içerisinde header.blade.php dosyasını ekleyerek bu helperlardan ikisini çağıracağız, aynı yapıyı koruduğunuz sürece bunlarla tekrar uğraşmanıza gerek kalmayacak.
<section class="content-header">
    {!! header_title() !!}
    {!! breadcrumbs() !!}
</section>
Artık sırasıyla her bir controller, form, model, request ve viewın içeriğini doldurmak kaldı. Açıkçası bu kısma gelebilmek bile büyük uğraş istiyor farkındayım ama bu aşamadan sonrası biraz tekrar gibi olacak, o yüzden sadece iki tane model ve controller örneği vereceğim, onlar da en temelleri olduğu için, gerisini de kendiniz deneyip, takıldığınız noktalarda Github'tan bakabilirsiniz.

Language kısmından başlayacak olursak, app/Language.php dosyasını açıp içeriğini alttaki gibi oluşturun.
class Language extends Model {

protected $fillable = ['title', 'flag', 'code', 'site_title', 'site_description'];

public function categories()
{
return $this->hasMany('App\Category');
}

public function pages()
{
return $this->hasMany('App\Page');
}

}
Bu ilk modeliniz, fillable şeklinde geçen kısımlar, herhangi bir Form request'i gerçekleştiğinde, kullanıcı tarafından sağlanabilir olan alanları belirtiyor. Neden buna ihtiyaç var diye soracak olursanız, örneğin birisi formun içine id diye kendi hayali alanını enjekte etti ( bildiğiniz üzere, formu göndermeden önce öğeyi denetle vb. araçlarla HTML'ini değiştirebilirsiniz ) ve siz de bunu kontrol etmeden direkt olarak tüm gelen POST ya da PUT isteğini aynen işlediniz, bunun sonucunda istemediğiniz bir şey gerçekleşir ve veriniz manipüle edilmiş olur, işte bunu önlemek için fillable şeklinde bir kısım tanımlıyoruz, gerisini de Laravel hallediyor. Peki ya bu hasMany'ler ne diye merak ediyorsanız, tablolarınız arasındaki bağlantıları sağlayan kısım da işte bu oluyor. Örneğin kurduğumuz yapıda bir dilin sahip olduğu birden çok kategori ve sayfa olabilir, işte bunlara ulaşmak için örneğin kategoriler için, categories diye bir fonksiyon oluşturarak, bunlara $language->categories() şeklinde ulaşabiliyoruz, örneğin o fonksiyonun adını kategoriler yapsaydınız da o dile ait kategorilere ulaşmak için $language->kategoriler() şeklinde ulaşacaktınız, yani o fonksiyonun ismi burada belirttiğiniz fonksiyon ismiyle aynı. Peki bu bağlantı nasıl sağlanıyor diye merak ediyorsanız, en başta yaptığımız migrationlarda bu referansı vermiştik, dikakt ederseniz her bir kategorinin ya da sayfanın language_id adlı bir foreign key'i var, işte Laravel bu keyleri algılayarak size, o dile ait tüm kategorileri ya da sayfaları, sizin tonlarca join query'si yazmanıza gerek kalmadan, size sunabilir. Bu relationın yanında en çok kullanacağız bir diğer ilişki de belongsTo, örneğin sayfalar sadece bir dile ait olacağı için ya da kurduğumuz bu modelde ( ki sonradan değiştireceğiz ) her bir makale sadece bir kategoriye ait olabilir, işte bu gibi durumlarda belongsTo'yu kullanabilirsiniz, birazdan tekrar bu kısmı inceleyeceğiz. Örneğin, bir makale birden çok kategoriye ait olabilirken, bir kategorinin de birden çok makalesi olursa, o zaman da belongsToMany ilişkisini kullanabiliriz.

Sıra controllera gelmeden önce LanguageRequest dosyasını düzenlememiz lazım. Bu request dosyaları validation yaptığımız kısım olarak düşünebilirsiniz.

use App\Http\Requests\Request;
use App\Language;

class LanguageRequest extends Request {

public function authorize()
{
return true;
}

public function rules()
{
return [
'title' => 'required|min:3',
'code' => 'required|max:6|unique:languages,code,'.$this->segment(3),
'site_title' => 'required|max:160',
'site_description' => 'required|max:160',
'flag' => 'sometimes|max:2048|image'
];
}

}
Burada açık olmayan, anlamayabileceğiniz iki kısım var, birincisi code alanı için olan unique kısmı, burada 3. segment ile ulaştığımız kısım admin/language/12 şeklindeki bir url'de 12 oluyor ve eğer kullanıcı edit işlemi yapıyorsa, o id'ye sahip olanda bu validation'ı gerçekleştirmiyor, dolayısıyla siz de aynı şeyi kaydettiğinizde, bu kod unique yani tekil değil hatası almıyorsunuz. Diğeri de sometimes kısmı, kullanıcı eğer bir bayrak yüklerse yani bir dosya yüklerse, o zaman validation'a sokuyoruz, sometimes de bunu belirtiyor. Ufak bir not da authorize methoduyla ilgili, burada biz authorize'ı routing'te middleware ile hallettiğimiz için, zaten kullanıcılar ulaşamayacağından true olarak döndürüyoruz, ama örneğin, aynı form requesti kullanan birkaç farklı kullanıcı grubu olduğunu düşünün, örneğin editör ve admin, editöre bazı kısıtlamalar getirmek istiyorsanız, bu kısmı kullanmanız sizin işinize yarayabilir.

Şimdi biz id'yi kullanıyoruz ama başka bir belirleyici, örneğin slug vb. şeyler kullanabilirsiniz, bunun için de app/Providers/RouteServiceProvider.php dosyasını değiştirmemiz gerekiyor. Bu sayede controller içerisinde id ile referans verip, her seferinde tek tek kaynağı bul işlemine sokup, kodu kalabalıklaştırmaktansa, bunu bind edebiliriz ki ileride de belirttiğim gibi bir şeyi değiştirecek olursanız çok kolay değiştirebilirsiniz.
use App\Language;
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider {

protected $namespace = 'App\Http\Controllers';

public function boot(Router $router)
{
parent::boot($router);
// bind article
$router->model('article', 'App\Article');
$router->bind('admin.article', function($id)
{
return \App\Article::findOrFail($id);
});
// bind category
$router->model('category', 'App\Category');
$router->bind('admin.category', function($id)
{
return \App\Category::findOrFail($id);
});
// bind language
$router->model('language', 'App\Language');
$router->bind('admin.language', function($id)
{
return \App\Language::findOrFail($id);
});
// bind language
$router->model('page', 'App\Page');
$router->bind('admin.page', function($id)
{
return \App\Page::findOrFail($id);
});
// bind setting
$router->model('setting', 'App\Setting');
$router->bind('admin.setting', function($id)
{
return \App\Setting::findOrFail($id);
});
// bind user
$router->model('user', 'App\User');
$router->bind('admin.user', function($id)
{
return \App\User::findOrFail($id);
});
}

public function map(Router $router)
{
$router->group(['namespace' => $this->namespace], function($router)
{
require app_path('Http/routes.php');
});
}

}
Gördüğünüz gibi bir kaynakta bir parametre paslandığında, onu direkt olarak bulmasını, bulamazsa da hata döndürmesini belirtiyoruz. Artık controller'da da bunu kullanabiliriz, böylece her bir şeye direkt olarak modeliyle ulaşabileceğiz. app/Http/Controllers/Admin/LanguageController.php dosyasının içeriği de alttaki gibi olacak.

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Http\Requests\LanguageRequest;
use App\Language;
use Laracasts\Flash\Flash;
use Kris\LaravelFormBuilder\FormBuilder;
use Datatable;

class LanguageController extends Controller {

public function index()
{
$table = $this->setDatatable();
return view('admin.languages.index', compact('table'));
}

public function create(FormBuilder $formBuilder)
{
$form = $formBuilder->create('App\Forms\LanguagesForm', [
'method' => 'POST',
'url' => route('admin.language.store')
]);
return view('admin.languages.create', compact('form'));
}

public function store(LanguageRequest $request)
{
$data = $this->storeImage($request, 'flag');
Language::create($data) == true ? Flash::success(trans('admin.create.success')) :
Flash::error(trans('admin.create.fail'));
return redirect(route('admin.language.index'));
}

public function show(Language $language)
{
return view('admin.languages.show', compact('language'));
}

public function edit(Language $language, FormBuilder $formBuilder)
{
$form = $formBuilder->create('App\Forms\LanguagesForm', [
'method' => 'PUT',
'url' => route('admin.language.update', ['id' => $language->id]),
'model' => $language
]);
return view('admin.languages.edit', compact('form', 'language'));
}

public function update(Language $language, LanguageRequest $request)
{
$data = $this->storeImage($request, 'flag');
$language->fill($data);
$language->save() == true ? Flash::success(trans('admin.update.success')) :
Flash::error(trans('admin.update.fail'));
return redirect(route('admin.language.index'));
}

public function destroy(Language $language)
{
$language->delete() == true ? Flash::success(trans('admin.delete.success')) :
Flash::error(trans('admin.delete.fail'));
return redirect(route('admin.language.index'));
}

private function storeImage(LanguageRequest $request, $field)
{
$data = $request->except([$field]);
if($request->file($field))
{
$file = $request->file($field);
$request->file($field);
$fileName = rename_file($file->getClientOriginalName(), $file->getClientOriginalExtension());
$path = '/uploads/' . str_plural($field);
$move_path = public_path() . $path;
$file->move($move_path, $fileName);
$data[$field] = $path . $fileName;
}
return $data;
}

private function setDatatable()
{
return Datatable::table()
->addColumn(trans('admin.fields.language.title'), trans('admin.fields.language.code'), trans('admin.fields.updated_at'))
->addColumn(trans('admin.ops.name'))
->setUrl(route('admin.language.table'))
->setOptions(array('sPaginationType' => 'bs_normal', 'oLanguage' => trans('admin.datatables')))
->render();
}

public function getDatatable()
{
return Datatable::collection(Language::all())
->showColumns('title', 'code')
->addColumn('updated_at',function($model)
{
return $model->updated_at->toDateTimeString();
})
->addColumn('',function($model)
{
return get_ops('language', $model->id);
})
->searchColumns('title')
->orderColumns('title','code')
->make();
}

}
Aslında kodu okuyunca her şey çok açık ama açıklamam gerekirse, index içerisinde datatableları kullanıyoruz ve yapısı bu şekilde, sütunlarınızı ekleyip, hangi sütunda arama ya da sıralama yapabileceğinizi belirtiyorsunuz. Formları da yine Forms klasöründe yer alan dosyalarda oluşturuyoruz ki onun da içeriği altta, resim kısmı da eğer kullanıcı resim yüklerse kontrol edip, ona göre düzenleme yapıyoruz. Her bir işlem sonucunda kullanıcıya bu durumu bildiriyoruz ki her aşamayı takip edebilsin. Formu edit kısmında nasıl çağırdığımıza da dikkat edin, model belirtiyoruz ki böylece edit kısmında veriler formun içerisinde dolu olarak geliyor. Bu aşamayla ilgili sorunuz olursa belirtin, açık olduğunu düşünmeme rağmen sizin anlayamadığınız kısımlar elbette olabilir. app/Forms/LanguagesForm.php dosyasının içeriği de alttaki gibi.
use Kris\LaravelFormBuilder\Form;

class LanguagesForm extends Form
{
public function buildForm()
{
$this
->add('title', 'text', [
'label' => trans('admin.fields.language.title')
])
->add('code', 'text', [
'label' => trans('admin.fields.language.code')
])
->add('site_title', 'text', [
'label' => trans('admin.fields.language.site_title')
])
->add('site_description', 'text', [
'label' => trans('admin.fields.language.site_description')
])
->add('flag', 'file', [
'label' => trans('admin.fields.language.flag'),
'attr' => ['class' => '']
])
->add('save', 'submit', [
'label' => trans('admin.fields.save'),
'attr' => ['class' => 'btn btn-primary']
])
->add('clear', 'reset', [
'label' => trans('admin.fields.reset'),
'attr' => ['class' => 'btn btn-warning']
]);
}
}
Ve son olarak sırasıyla viewlar, farkedeceksiniz ki viewlar olabildiğince sade ki bu bizim için çok iyi bir şey.

views/admin/languages/create.blade.php
@extends('layouts.admin')

@section('content')
{!! form($form) !!}
@endsection

views/admin/languages/edit.blade.php
@extends('layouts.admin')

@section('content')
    {!! form($form) !!}
    @unless ($language->flag == "")
    <div class="uploaded-file">
        <strong>{{ trans('admin.fields.uploaded')  }}</strong>
        <img class="img-responsive" alt="" src="{!! $language->flag  !!}" />
    </div>
    @endunless
@endsection
views/admin/languages/index.blade.php
@extends('layouts.admin')

@section('content')
{!! $table !!}
@endsection
views/admin/languages/show.blade.php
@extends('layouts.admin')

@section('content')
    <img class="img-responsive" alt="" src="{!! $language->flag  !!}" />
    <h1> {{ $language->title  }} ({{ $language->code  }})</h1>
    <h2> {{ trans('admin.fields.language.site_title') . ': ' . $language->site_title  }}</h2>
    <h3> {{ trans('admin.fields.language.site_description') . ': ' . $language->site_description  }}</h3>
@endsection
Sadece çok ufak bir detaydan bahsedeceğim ve bu makaleyi sonlandıracağım, örneğin yeni bir kategori ekliyorsunuz, onun dilini seçmek için, tüm dilleri çekip, diziden okutmanız lazım ki bu kısma geldikten sonra tek görmediğiniz kısım o olacak. Onu da nasıl yapacağınızı belirteyim, kategoriler için gerisini de siz yapmaya çalışın, yapamazsanız da Github'tan bakabilirsiniz.

Bu ve bunun gibi durumlarda örneğin create'i alttaki gibi oluşturabilirsiniz:
public function create(FormBuilder $formBuilder)
{
$languages = Language::lists('title', 'id');
$form = $formBuilder->create('App\Forms\CategoriesForm', [
'method' => 'POST',
'url' => route('admin.category.store')
], $languages);
return view('admin.categories.create', compact('form'));
}
Ve bu veriyi de form dosyanızda alttaki gibi kullanabilirsiniz.
public function buildForm()
{
$this
->add('language_id', 'choice', [
'choices' => $this->data,
'label' => trans('admin.fields.category.language_id')
]);
}
Gördüğünüz gibi gayet basit. Bir de bahsetmediğim diğer nokta slug oluşturma kısmı. Slug'ın ne olduğunu tekrardan açıklamam gerekirse, başlığı Müthiş Başlık olan bir makalenin url dostu bir biçimde çıkması için slug oluşturursunuz ve bu slug da muthis-baslik şeklinde olur, tamami ASCII karakterlerinden oluşan, küçük harflerle yazılmış ve boşluk yerine - ( dash ) gelmiş şekilde. Peki ya tekrar Müthiş Başlık başlığına sahip bir başka makale oluşturursanız ne olacak? Ki farkettiğiniz üzere slug'ı fillable yapmadık, bizim düzenlememiz gerek, bu durum için de bir eklenti mevcut ve onu kullanacağız, tekrar Müthiş Başlık başlığına sahip bir makale eklenirse onun slugı otomatik olarak muthis-baslik-1 olacak ve kullanıcı bunlarla uğraşmak zorunda kalmayacak. composer.json dosyasının require kısmına "cviebrock/eloquent-sluggable" : ">=3.0.0-alpha" satırını ekleyin ve öğrenmiş olduğunuz üzere config/app.php dosyanızın providers dizisine 'Cviebrock\EloquentSluggable\SluggableServiceProvider' satırını ekleyin. Örnek kategori modeli de alttaki gibi olacak, belongsTo kısmına da dikkat edin.

use Illuminate\Database\Eloquent\Model;
use Cviebrock\EloquentSluggable\SluggableInterface;
use Cviebrock\EloquentSluggable\SluggableTrait;

class Category extends Model implements SluggableInterface{

use SluggableTrait;

protected $fillable = ['title', 'description', 'color', 'language_id'];

protected $sluggable = array(
'build_from' => 'title',
'save_to' => 'slug',
'on_update' => true
);

public function language()
{
return $this->belongsTo('App\Language');
}

}
View dosyalarının da nasıl olduğunu da alttaki gibi görebilirsiniz. Altta datatable çıktısının nasıl olduğu bulunmakta.
Laravel Datatable

Bu da formlarımızın nasıl göründüğü.
Laravel Form

Hepsi bu kadar, slug da artık otomatik olarak oluşturuluyor, herhangi bir çakışma da olmuyor. Buraya kadar yaptığımız tüm kısımları ve kategorileri incelemek isterseniz Github'tan bu linke tıklayabilirsiniz: https://github.com/ozdemirburak/laravel-5-simple-cms/tree/d2d21713d42349c9329b40410518daac8db16f50

Seriye ait tüm makaleler alttaki gibidir.
  1. Laravel 5 ile CMS - Kurulum
  2. Laravel 5 ile CMS - Migration, Seed, Middleware, Elixir, Bower, Gulp, Blade
  3. Laravel 5 ile CMS - Controller, Model, Request, Provider, Form
  4. Laravel 5 ile CMS - WYSIWYG Filemanager, Çoklu Dil, Google Analitik API
  5. Laravel 5 ile CMS - Events, Email ve Frontend
  6. Laravel 5 ile CMS - FTP veya SSH ile Aktarım (Deployment)
Github üzerinden projenin son haline ulaşmak için: https://github.com/ozdemirburak/laravel-5-simple-cms