Bu makalede, Laravel 11 uygulamasında Echo, Socket.IO ve Redis sunucusu kullanarak gerçek zamanlı bildirimlerin nasıl gönderileceğini anlatacağım. Bu işlem için phpredis sürücüsünü kullanarak Echo sunucusu üzerinden Socket.IO ile gerçek zamanlı bildirimler göndereceğiz. Bu yöntemi Socket.IO ve Redis sunucusu ile tamamen ücretsiz ve açık kaynak olarak kullanabilirsiniz.
Socket.IO Nedir?
Socket.IO, web istemcileri ve sunucular arasında gerçek zamanlı, çift yönlü iletişim sağlayan bir JavaScript kütüphanesidir. WebSocket bağlantılarını kurmayı ve sürdürmeyi kolaylaştırarak verilerin etkin bir şekilde değiş tokuş edilmesini sağlar. Dinamik ve gerçek zamanlı uygulamalar geliştirmek isteyenler için, özellikle sohbet uygulamaları, canlı bildirimler ve çevrimiçi oyunlar gibi projelerde yaygın olarak kullanılır. Farklı tarayıcı ve ortamlarla uyumluluğu sayesinde gerçek zamanlı etkileşimleri daha erişilebilir ve güvenilir hale getirir.
Redis Sunucusu Nedir?
Redis Sunucusu, veritabanı, önbellek ve mesaj aracısı olarak kullanılan açık kaynaklı, bellek içi bir veri deposudur. String, liste, küme ve hash gibi çeşitli veri yapılarıyla uyumlu çalışır. Hızıyla tanınan Redis, veriye hızlı erişim sağlayarak gerçek zamanlı uygulamalar için idealdir. Genellikle sıkça erişilen verilerin bellekte saklanarak web performansını artırmak için kullanılır, böylece daha yavaş disk depolama erişimine ihtiyaç azaltılmış olur.
Laravel 11 ile Gerçek Zamanlı Bildirim Gönderme Adımları (Socket.IO ve Redis)
Gerçek zamanlı bildirimler göndermek için Laravel 11'de Socket.IO ve Redis kullanarak adım adım ilerleyelim.
Adım 1: Laravel 11 Kurulumu
Laravel 11'i projeye kurarak başlayın.
Adım 8: Rotaları (Routes) Tanımlama
Bildirim işlemleri ve event'leri çalıştırmak için gerekli rotaları web.php dosyasına ekleyin.
Adım 9: Controller Oluşturma
Gerçek zamanlı bildirimleri yönetmek için bir controller oluşturun.
Adım 10: Blade Dosyalarını Oluşturma ve Güncelleme
Bildirimleri gösterecek Blade dosyalarını oluşturarak, bildirimlerin görünüm yapısını güncelleyin.
Adım 11: Yönetici Kullanıcı (Admin User) Oluşturma
Gerçek zamanlı bildirimleri yönetmek için bir yönetici (admin) kullanıcı oluşturun.
Adım 12: Node.js'i 6001 Portu için Ayarlama
Socket.IO sunucusunu çalıştırmak için 6001 portunu kullanacak şekilde Node.js ayarlarını yapılandırın.
Laravel Uygulamasını Çalıştırma
Son olarak, Laravel uygulamanızı başlatın:
composer create-project laravel/laravel ornek-uygulama
composer require laravel/ui
Auth (Kimlik Doğrulama) İşlevlerini Oluşturma
php artisan ui bootstrap --auth
NPM Bağımlılıklarını Yükleme ve Derleme
npm install
npm run build
Adım 3: Migration Dosyalarını Oluşturma
Bu adımda, posts tablosunu oluşturacak ve users tablosuna is_admin adında bir sütun ekleyeceğiz. Aşağıdaki komutları çalıştırarak ilgili migration dosyalarını oluşturalım:
php artisan make:migration add_is_admin_column_to_users_table
php artisan make:migration create_posts_table
Şimdi, oluşturulan migration dosyalarını güncelleyelim:
database/migrations/2024_06_18_140624_add_is_admin_column_to_users_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->tinyInteger('is_admin')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};
database/migrations/2024_06_18_140906_create_posts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Migration Komutunu Çalıştırma
Son olarak, oluşturulan tüm migration dosyalarını veritabanına uygulamak için aşağıdaki komutu çalıştırın:
php artisan migrate
Bu işlemlerle, posts tablosu oluşturulacak ve users tablosuna yönetici rolünü belirlemek için is_admin sütunu eklenecektir.
Adım 4: Modelleri Oluşturma ve Güncelleme
Bu adımda, Post modelini oluşturacağız ve User modelini güncelleyeceğiz. Beğeni ve beğenmeme işlemleri için ilişkiler ve bazı model fonksiyonları tanımlanacaktır.
Post Modelini Oluşturma
Öncelikle, Post modelini aşağıdaki komut ile oluşturun:
php artisan make:model Post
Ardından, Post modelinde hasMany() ilişkisini tanımlayarak dosyayı güncelleyin:
app/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'body', 'user_id'];
/**
* Write code on Method
*
* @return response()
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
User Modelini Güncelleme
User modelini güncelleyerek Post modeli ile ilişkisini kuracağız. Dosyada fillable, hidden, ve casts özellikleri de bulunmaktadır.
app/Models/User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'is_admin'
];
/**
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
Bu dosyaları güncelledikten sonra, Post modelinin User modeli ile ilişkisi tanımlanmış olacak. User modelinde ise fillable, hidden, ve casts özellikleri üzerinden kullanıcı bilgilerini yapılandırmış olacaksınız.
Adım 5: Redis Sunucusunu Kurma
Bu adımda, sistemimize Redis sunucusunu kuracağız. İlk olarak, aşağıdaki komutu çalıştırarak Redis sunucusunu sisteminize kurun:
Redis Sunucusunu Yükleme
sudo apt-get update
sudo apt-get install redis-server
Ardından, PHP ile Redis kullanımını sağlamak için php-redis paketini yükleyin:
PHP-Redis Paketini Yükleme
sudo apt-get install php-redis
Son olarak, Redis sunucusunu başlatmak için şu komutu çalıştırın:
Redis Sunucusunu Başlatma
sudo systemctl start redis
Redis sunucusunun durumunu kontrol etmek için şu komutu kullanabilirsiniz:
Redis Sunucusunun Durumunu Kontrol Etme
sudo systemctl status redis
Adım 6: Echo Sunucusunu ve Socket.IO'yu Kurma
Bu adımda, Laravel yayıncılığını yapılandıracağız ve Socket.IO'yu Redis sürücüsü olarak kullanacağız.
Laravel Broadcast Özelliğini Etkinleştirme
Öncelikle, Laravel'de yayıncılık (broadcasting) varsayılan olarak etkin değildir. Bu yüzden, yayıncılığı etkinleştirmek için aşağıdaki komutu çalıştırın:
php artisan install:broadcasting
Predis/Predis Paketini Kurma
Yayıncı olarak Redis kullanabilmek için, predis/predis paketini kurmamız gerekiyor. Bu komutu çalıştırarak kurulum işlemini yapabilirsiniz:
composer require predis/predis
Laravel Echo ve Socket.IO Client Kurulumu
Laravel Echo sunucusunu kurmak için aşağıdaki komutları çalıştırarak gerekli paketleri yükleyin:
npm install --save laravel-echo socket.io-client
Şimdi, resources/js/echo.js dosyasındaki kodu aşağıdaki gibi güncelleyin:
resources/js/echo.js
import Echo from 'laravel-echo';
import io from 'socket.io-client';
window.io = io;
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
.env Dosyasını Güncelleme
Uygulamanızdaki .env dosyasını açın ve BROADCAST_CONNECTION ile REDIS_CLIENT ortam değişkenlerini redis olarak ayarlayın. Ayrıca, Redis'e ait diğer çevresel değişkenleri de ekleyin:
.env
BROADCAST_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Şimdi, JavaScript dosyasını yeniden derlemek için aşağıdaki komutu çalıştırın:
npm run build
config/database.php Dosyasını Güncelleme
Redis yapılandırmalarını doğru şekilde uygulamak için config/database.php dosyasını kontrol edin ve aşağıdaki şekilde güncelleyin:
config/database.php
<?php
use Illuminate\Support\Str;
return [
...
'redis' => [
'client' => env('REDIS_CLIENT', 'redis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => '',
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];
config/broadcasting.php Dosyasını Güncelleme
Son olarak, config/broadcasting.php dosyasına Redis sürücüsünü ekleyin:
config/broadcasting.php
<?php
return [
'default' => env('BROADCAST_CONNECTION', 'null'),
'connections' => [
...
'log' => [
'driver' => 'log',
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
],
];
Bu işlemleri tamamladıktan sonra, Echo sunucusu ve Socket.IO ile Redis üzerinden yayın yapmaya hazır olacaksınız.
Adım 7: PostCreate Olayını Oluşturma
Bu adımda, Laravel Artisan komutunu kullanarak bir "Event" (olay) oluşturacağız. Aşağıdaki komutla PostCreate adlı olay sınıfını oluşturacağız.
PostCreate Olayı Sınıfını Oluşturma
Aşağıdaki komutu çalıştırarak yeni bir olay sınıfı oluşturun:
php artisan make:event PostCreate
Bu komutu çalıştırdıktan sonra, app klasöründe yeni bir "Events" adlı klasör oluşacaktır. Bu klasörde, aşağıdaki gibi değişiklikler yapmamız gerekiyor:
app/Events/PostCreate.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class PostCreate implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $post;
/**
* Create a new event instance.
*/
public function __construct($post)
{
$this->post = $post;
}
/**
* Write code on Method
*
* @return response()
*/
public function broadcastOn()
{
return new Channel('posts');
}
/**
* Write code on Method
*
* @return response()
*/
public function broadcastAs()
{
return 'create';
}
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith(): array
{
return [
'message' => "[{$this->post->created_at}] New Post Received with title '{$this->post->title}'."
];
}
}
Adım 8: Yönlendirmeleri (Routes) Oluşturma
Bu adımda, gerçek zamanlı bildirimler için bazı yönlendirmeleri (routes) oluşturacağız. "routes/web.php" dosyanızı açın ve aşağıdaki yönlendirmeleri ekleyin.
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
Adım 9: Controller Oluşturma
Bu adımda, bir PostController denetleyicisi oluşturmamız gerekiyor. Bu denetleyici, bildirim gönderecek bir index metoduna sahip olacak. Aşağıdaki kodu kullanarak denetleyicimizi oluşturabiliriz.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Post;
use App\Events\PostCreate;
class PostController extends Controller
{
/**
* Write code on Method
*
* @return response()
*/
public function index(Request $request)
{
$posts = Post::get();
return view('posts', compact('posts'));
}
/**
* Write code on Method
*
* @return response()
*/
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required',
'body' => 'required'
]);
$post = Post::create([
'user_id' => auth()->id(),
'title' => $request->title,
'body' => $request->body
]);
event(new PostCreate($post));
return back()->with('success','Post created successfully.');
}
}
Adım 10: Blade Dosyalarını Oluşturma ve Güncelleme
Bu adımda, app.blade.php dosyasını güncelleyecek ve yeni bir posts.blade.php dosyası oluşturacağız. Aşağıdaki gibi güncellenmiş kodu kullanabilirsiniz.
resources/views/layouts/app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
@yield('script')
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
Laravel Send Realtime Notification using SocketIO Redis Server
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item">
<a class="nav-link" href="{{ route('posts.index') }}">{{ __('Posts') }}</a>
</li>
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
resources/views/posts.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header"><i class="fa fa-list"></i> {{ __('Posts List') }}</div>
<div class="card-body">
@session('success')
<div class="alert alert-success" role="alert">
{{ $value }}
</div>
@endsession
<div id="notification">
</div>
@if(!auth()->user()->is_admin)
<p><strong>Create New Post</strong></p>
<form method="post" action="{{ route('posts.store') }}" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label>Title:</label>
<input type="text" name="title" class="form-control" />
@error('title')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label>Body:</label>
<textarea class="form-control" name="body"></textarea>
@error('body')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div class="form-group mt-2">
<button type="submit" class="btn btn-success btn-block"><i class="fa fa-save"></i> Submit</button>
</div>
</form>
@endif
<p class="mt-4"><strong>Post List:</strong></p>
<table class="table table-bordered data-table">
<thead>
<tr>
<th width="70px">ID</th>
<th>Title</th>
<th>Body</th>
</tr>
</thead>
<tbody>
@forelse($posts as $post)
<tr>
<td>{{ $post->id }}</td>
<td>{{ $post->title }}</td>
<td>{{ $post->body }}</td>
</tr>
@empty
<tr>
<td colspan="5">There are no posts.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
@if(auth()->user()->is_admin)
<script type="module">
window.Echo.connector.socket.on('connect', () => {
console.log('Successfully connected to Socket.IO server');
});
window.Echo.channel('posts')
.listen('.create', (data) => {
console.log('Order status updated: ', data);
var d1 = document.getElementById('notification');
d1.insertAdjacentHTML('beforeend', '<div class="alert alert-success alert-dismissible fade show"><span><i class="fa fa-circle-check"></i> '+data.message+'</span></div>');
});
</script>
@endif
@endsection
Step 11: Yönetici Kullanıcısı Oluşturma
Bu adımda, yönetici kullanıcısı oluşturmak için seeder komutunu çalıştırmamız gerekiyor.
Öncelikle şu komutu çalıştırarak seeder'ı oluşturuyoruz:
php artisan make:seeder CreateAdminUser
Şimdi, CreateAdminUser seeder dosyasını güncellememiz gerekiyor.
database/seeders/CreateAdminUser.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\User;
class CreateAdminUser extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::create([
'name' => 'Admin',
'email' => 'admin@gmail.com',
'password' => bcrypt('123456'),
'is_admin' => 1
]);
}
}
Ardından, seeder'ı çalıştırmak için şu komutu kullanıyoruz:
php artisan db:seed --class=CreateAdminUser
Adım 12: 6001 Portu İçin Node JS Kurulumu
Bu adımda, Socket.IO kanalı için 6001 portunu etkinleştireceğiz. Bunun için aşağıdaki npm komutlarını çalıştırarak gerekli paketleri yüklemelisiniz:
npm install --save ioredis
npm install --save socket.io
Şimdi, kök dizine server.js dosyasını oluşturuyoruz ve aşağıdaki kodu ekliyoruz:
server.js
import { createServer } from 'http';
import { Redis } from 'ioredis';
import { Server } from 'socket.io';
const server = createServer();
const io = new Server(server, {
cors: {
origin: "*",
}
});
const redis = new Redis();
redis.subscribe('posts', (err, count) => {
if (err) {
console.error('Failed to subscribe: %s', err.message);
} else {
console.log(`Subscribed successfully! This client is currently subscribed to ${count} channels.`);
}
});
redis.on('message', (channel, message) => {
const event = JSON.parse(message);
console.log(`Message received from channel ${event.event}: ${channel}`);
io.emit(event.event, channel, event.data);
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(6001, () => {
console.log('listening on *:6001');
});
Tüm gerekli adımlar tamamlandı, şimdi Laravel uygulamanızı çalıştırmak için aşağıdaki komutu yazın ve Enter tuşuna basın:
php artisan serve
Ardından, 6001 portu için Node.js sunucusunu çalıştırmak için şu komutu yazın:
node server.js
Son olarak, web tarayıcınıza gidin, aşağıdaki URL'yi yazın ve uygulamanın çıktısını görüntüleyin:
http://localhost:8000/
Şimdi, bir admin kullanıcınız var ve normal kullanıcıyı kayıt formu üzerinden kaydedebilirsiniz.
Yeni bir yazı oluşturabilirsiniz ve admin kullanıcı bu yazı hakkında bildirim alacaktır.
