Skip to content

ahmad-syaifuddin/panduan-deploy-laravel-to-infinityfree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

25 Commits
Β 
Β 

Repository files navigation

πŸ›°οΈ Panduan Deploy Laravel ke InfinityFree (Anti-Gagal)

Panduan lengkap untuk deploy aplikasi Laravel ke hosting gratis InfinityFree dengan metode yang telah terbukti stabil dan aman.

Laravel InfinityFree PHP


πŸ“‹ Daftar Isi


🧰 Prasyarat

Pastikan kamu sudah memiliki:

  • βœ… Laravel versi 8, 9, 10 atau max laravel 11
  • βœ… Aplikasi berjalan normal di lokal
  • βœ… Akun InfinityFree aktif dengan domain/subdomain
  • βœ… FTP Client (FileZilla) atau akses File Manager
  • βœ… Composer terinstall di lokal

πŸ“ Struktur Direktori

Struktur ini memastikan keamanan optimal di shared hosting dengan memisahkan file publik dan privat:

htdocs/
β”œβ”€β”€ index.php         ← Entry point (dari public/)
β”œβ”€β”€ .htaccess         ← URL rewriting rules
β”œβ”€β”€ assets/           ← Isi dari folder public/ Laravel (css, js, images)
β”‚   β”œβ”€β”€ css/
β”‚   β”œβ”€β”€ js/
β”‚   └── images/
└── laravel/          ← Core Laravel application
    β”œβ”€β”€ app/
    β”œβ”€β”€ bootstrap/
    β”œβ”€β”€ config/
    β”œβ”€β”€ database/
    β”œβ”€β”€ resources/
    β”œβ”€β”€ routes/
    β”œβ”€β”€ storage/
    β”œβ”€β”€ vendor/       ← Dependencies (file terbesar)
    β”œβ”€β”€ artisan
    β”œβ”€β”€ .env
    └── composer.json

🚨 Masalah Utama InfinityFree

InfinityFree memiliki keterbatasan environment yang mempengaruhi deployment Laravel:

  • No Composer CLI: Tidak bisa jalankan composer install di server
  • PHP Extensions: Terbatas pada extension bawaan, tidak bisa install custom
  • File Structure: Path public harus disesuaikan dengan struktur hosting
  • Bootstrap Method: Laravel 11+ menggunakan method baru yang tidak kompatibel
  • CLI Commands: Artisan commands terbatas atau tidak berfungsi

πŸ“Š Compatibility Matrix

Laravel Version Bootstrap Method InfinityFree Status Action Required
Laravel 10.x Classic (new Application) βœ… Compatible Ready to deploy
Laravel 11.x Modern (Application::configure) ⚠️ Needs Fix Modify bootstrap file
Laravel 12.x Modern only ❌ Problematic Complex workaround

πŸ”§ Laravel 11 Fix for InfinityFree

Problem

Laravel 11 menggunakan bootstrap method baru di bootstrap/app.php:

<?php

use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })
    ->create();

Solution

Ganti dengan bootstrap method klasik (Laravel 10 style):

<?php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

Additional Files Needed (Opsi tapi di cek dulu ya)

Pastikan file-file berikut ada (copy dari Laravel 10 project):

  • app/Http/Kernel.php
  • app/Console/Kernel.php
  • app/Exceptions/Handler.php

Note

Laravel 11 Compatibility Reminder

Saat menggunakan gaya klasik di bootstrap/app.php, Laravel akan memanggil:

  • app/Http/Kernel.php
  • app/Console/Kernel.php
  • app/Exceptions/Handler.php

File ini sudah tersedia default di Laravel 11, jadi tidak perlu copy dari Laravel 10.
Cukup pastikan file tersebut tidak terhapus.

🎨 Breeze & Tailwind Version Guide

Laravel 10

  • Breeze: v1.19.x (latest compatible)
  • Tailwind: 3.2.x - 3.3.x
  • Installation: composer require laravel/breeze:^1.19

Laravel 11

  • Breeze: v2.x
  • Tailwind: 3.4.x
  • Installation: composer require laravel/breeze:^2.0

Laravel 12

  • Breeze: v3.x
  • Tailwind: 4.x (beta)
  • Installation: composer require laravel/breeze:^3.0

Bootstrap Fix (Laravel 11 only)

  • Backup original bootstrap/app.php
  • Replace dengan classic bootstrap method
  • Copy required Kernel files dari Laravel 10
  • Test local setelah modifikasi

Tested With: Laravel 10.48, 11.23 | Breeze 1.19, 2.1 | Tailwind 3.3, 3.4

πŸš€ Langkah Deploy

1. Persiapan Lokal

# Optimasi aplikasi untuk production
composer install --no-dev --optimize-autoloader
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan key:generate --show

⚠️ Penting: Simpan APP_KEY yang dihasilkan untuk file .env hosting.

2. Konfigurasi File Entry Point

Edit index.php (dari public/index.php)

<?php

use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

define('LARAVEL_START', microtime(true));

// Maintenance mode check
if (file_exists($maintenance = __DIR__.'/laravel/storage/framework/maintenance.php')) {
    require $maintenance;
}

// Autoloader
require __DIR__.'/laravel/vendor/autoload.php';

// Bootstrap application
$app = require_once __DIR__.'/laravel/bootstrap/app.php';

$kernel = $app->make(Kernel::class);

$response = $kernel->handle(
    $request = Request::capture()
)->send();

$kernel->terminate($request, $response);

Edit AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Fix public path untuk InfinityFree
        $this->app->bind('path.public', function() {
            return base_path('../');
        });
    }

    public function boot()
    {
        // Force HTTPS jika diperlukan
        if (env('FORCE_HTTPS', false)) {
            \URL::forceScheme('https');
        }
    }
}

Buat Custom Asset Helper

Karena folder public/ Laravel dipindah ke htdocs/assets/, kita perlu helper untuk path yang benar.

Buat file app/helpers.php:

<?php

if (!function_exists('public_asset')) {
    function public_asset($path)
    {
        // Untuk akses file di htdocs/assets/
        return url('/assets') . '/' . ltrim($path, '/');
    }
}

Daftarkan di composer.json:

{
    "autoload": {
        "files": [
            "app/helpers.php"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    }
}

3. Upload ke Hosting

⚠️ Batasan: InfinityFree memiliki limit upload 8MB per file. Folder vendor/ harus diupload bertahap.

Strategi Upload Bertahap

Step 1: Upload File Utama Dulu

  1. Isi htdocs/: Upload index.php dan .htaccess (yang sudah diedit) dari folder public/ lokalmu ke htdocs/
  2. Upload assets: Pindahkan isi folder public/ (css, js, images) ke folder assets/ di htdocs/
  3. Buat folder laravel/: Di dalam htdocs/, buat folder baru bernama laravel
  4. Upload folder Laravel:
htdocs/laravel/
β”œβ”€β”€ app/              ← Upload pertama
β”œβ”€β”€ bootstrap/        ← Upload kedua  
β”œβ”€β”€ config/           ← Upload ketiga
β”œβ”€β”€ database/         ← Upload keempat
β”œβ”€β”€ resources/        ← Upload kelima
β”œβ”€β”€ routes/           ← Upload keenam
β”œβ”€β”€ storage/          ← Upload ketujuh
β”œβ”€β”€ .env              ← Upload kedelapan
β”œβ”€β”€ artisan           ← Upload kesembilan
β”œβ”€β”€ composer.json     ← Upload kesepuluh
└── file lainnya...   ← Upload kesebelas

Step 2: Upload Vendor (Pilih Metode)

πŸ”₯ Metode A: Upload Bertahap
# Upload satu per satu folder vendor/
vendor/
β”œβ”€β”€ symfony/     ← Upload pertama (terbesar)
β”œβ”€β”€ laravel/     ← Upload kedua
β”œβ”€β”€ composer/    ← Upload ketiga
└── ...          ← Lanjutkan bertahap
πŸ’‘ Metode B: Kompresi
# Di lokal, split vendor menjadi bagian kecil
cd vendor/
tar -czf ../vendor-part1.tar.gz symfony/ laravel/
tar -czf ../vendor-part2.tar.gz composer/ psr/ monolog/

Upload file .tar.gz lalu extract via File Manager cPanel.

πŸš€ Metode C: ZIP + Extract Script

Di lokal:

# Kompres folder vendor menjadi zip
cd your-laravel-project/
zip -r vendor.zip vendor/

Upload ke hosting:

  1. Upload file vendor.zip ke folder htdocs/
  2. Buat file extract.php di htdocs/ dengan kode berikut:
<?php
$zip = new ZipArchive;
$path = __DIR__ . '/laravel/vendor'; // direktori tujuan ekstrak
$namaZip = 'test.zip'; 

if (!is_dir($path)) {
    mkdir($path, 0755, true);
}

if ($zip->open($namaZip) === TRUE) {
    $zip->extractTo($path);
    $zip->close();
    echo "Vendor extracted to laravel/vendor successfully βœ….<br>";

    // βœ… Pastikan nama file yang dihapus sama dengan yang dibuka
    if (unlink($namaZip)) {
        echo "$namaZip deleted.";
    } else {
        echo "Failed to delete $namaZip.";
    }
} else {
    echo "Failed to open $namaZip.";
}
?>
  1. Akses http://yourdomain.page.gd/extract.php via browser
  2. Tunggu proses extract selesai
  3. Hapus file extract.php setelah selesai untuk keamanan

⚠️ Catatan Penting:

  • File vendor.zip mungkin besar (20-50MB), pastikan koneksi stabil
  • Proses extract bisa memakan waktu 2-5 menit
  • Selalu hapus extract.php setelah digunakan

βš™οΈ Pengaturan FileZilla

Transfer Settings:
βœ“ Concurrent transfers: 1
βœ“ Maximum transfers: 1
βœ“ Timeout: 60 seconds
βœ“ Passive mode: enabled
βœ“ Transfer mode: Binary

4. Konfigurasi Environment

Edit .env di htdocs/laravel/:

APP_NAME="Your App Name"
APP_ENV=production
APP_DEBUG=false
APP_URL=http://yourdomain.page.gd
APP_KEY=base64:your_generated_key_here

# Database credentials dari InfinityFree
DB_CONNECTION=mysql
DB_HOST=sqlXXX.page.gd
DB_PORT=3306
DB_DATABASE=epiz_xxxxxxxx_dbname
DB_USERNAME=epiz_xxxxxxxx
DB_PASSWORD=your_db_password

# Session & Cache
SESSION_DRIVER=file
CACHE_DRIVER=file
QUEUE_CONNECTION=database

# Disable untuk hosting gratis
BROADCAST_DRIVER=log
MAIL_MAILER=log

# Timezone
APP_TIMEZONE=Asia/Makassar

# Optional
FORCE_HTTPS=false

5. Setup .htaccess

File .htaccess di htdocs/:

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Front Controller
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

# Security Headers
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options DENY
    Header always set X-XSS-Protection "1; mode=block"
</IfModule>

# Disable Directory Browsing
Options -Indexes

# Protect Sensitive Files
<FilesMatch "\.(env|log|htaccess)$">
    Order Allow,Deny
    Deny from all
</FilesMatch>

6. Database Setup

  1. Buat database di cPanel InfinityFree
  2. Import file SQL via phpMyAdmin
  3. Jalankan migrasi jika menggunakan session database:
# Di lokal
php artisan session:table
php artisan cache:table
php artisan migrate

7. Testing

Buat route test di routes/web.php:

Route::get('/test-db', function () {
    try {
        \DB::connection()->getPdo();
        return "βœ… Database connection successful!";
    } catch (\Exception $e) {
        return "❌ Database failed: " . $e->getMessage();
    }
});

Akses: http://yourdomain.page.gd/test-db


⚠️ Troubleshooting

πŸ”΄ Kendala Umum & Solusi

Masalah Gejala Solusi
Error 500 / Halaman Putih Server Error, halaman kosong β€’ Aktifkan APP_DEBUG=true sementara
β€’ Periksa path di index.php
β€’ Cek permission storage/ (755)
β€’ Validasi syntax .env
Asset (CSS/JS) Tidak Load Style tidak muncul, 404 error β€’ Gunakan helper public_asset()
β€’ Pastikan folder assets/ di htdocs/
β€’ Cek console browser untuk error
Session Error Login logout terus, session hilang β€’ Pastikan tabel sessions ada
β€’ Set SESSION_DRIVER=database
β€’ Cek permission storage/framework/sessions/
Database Connection Failed Error koneksi database β€’ Verifikasi kredensial di .env
β€’ Pastikan database sudah dibuat
β€’ Test dengan route /test-db
403 Forbidden Akses ditolak β€’ Periksa permission folder
β€’ Cek file .htaccess
β€’ Pastikan index.php ada
Mixed Content Error HTTPS/HTTP conflict β€’ Set FORCE_HTTPS=true
β€’ Update semua URL ke HTTPS
Upload Gagal/Timeout Upload terputus, file rusak β€’ Upload bertahap untuk vendor/
β€’ Set FileZilla 1 concurrent transfer
β€’ Upload di jam sepi (malam)
Vendor Tidak Lengkap Class not found error β€’ Cek semua package di vendor/
β€’ Gunakan metode kompresi
β€’ Verifikasi autoload.php

πŸ”΄ Kendala Tambahan (Studi Kasus Real)

❌ 1. Error: Cannot resolve public path saat Generate PDF

Penyebab: InfinityFree tidak mengenali folder public/ Laravel karena struktur direktori diubah.

Gejala:

ErrorException: file_get_contents(public/css/app.css): failed to open stream

Solusi:

// Di AppServiceProvider.php method register()
$this->app->bind('path.public', function () {
    return base_path('../');
});

Checklist tambahan:

  • βœ… File composer.json ada di htdocs/laravel/
  • βœ… Hapus semua cache di bootstrap/cache/
  • βœ… Package barryvdh/laravel-dompdf terupload lengkap

❌ 2. Error: file_get_contents(...composer.json): Failed to open stream

Penyebab: File composer.json belum terupload ke hosting.

Solusi: Upload file composer.json ke folder htdocs/laravel/

❌ 3. PDF Generator Error saat Pdf::loadView()

Penyebab: Error resolve asset saat path public tidak cocok.

Solusi Alternatif:

// Ganti dari:
$pdf = Pdf::loadView('tabungan.pdf', compact(...));

// Menjadi:
$view = view('tabungan.pdf', compact(...))->render();
$pdf = Pdf::loadHtml($view);

❌ 4. TailwindCSS Tidak Ter-render

Penyebab: Helper asset() mengarah ke path Laravel asli.

Solusi: Gunakan helper kustom:

<!-- Ganti dari: -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">

<!-- Menjadi: -->
<link href="{{ public_asset('assets/css/app.css') }}" rel="stylesheet">

❌ 5. Laravel Mix / Vite Build Assets Error

Penyebab: File hasil build tidak dikenali struktur hosting.

Solusi:

  1. Pastikan file hasil npm run build terupload ke htdocs/build/
  2. Gunakan path manual:
<link rel="stylesheet" href="{{ asset('build/assets/app-Bu5eBVKL.css') }}">
<script src="{{ asset('build/assets/app-CLAht3ih.js') }}" defer></script>

πŸ’‘ Tip: Nama file hash berubah setiap build. Cek nama file yang tepat di folder build/assets/


Dokumentasi Lengkap: 3 Metode Penanganan Upload File di Laravel untuk Shared Hosting (khususnya InfinityFree)

Versi: 1.2 Tanggal: 23 Agustus 2025 Proyek: BlackFile

Abstrak

Dokumen ini adalah panduan teknis yang membandingkan tiga metode untuk menangani upload file pada aplikasi Laravel di lingkungan shared hosting dengan struktur direktori non-standar. Tujuannya adalah untuk memberikan pemahaman mendalam tentang setiap pendekatan, mulai dari yang paling sederhana hingga yang paling tangguh dan portabel.


1. Konteks Masalah Universal

Pada banyak platform shared hosting (contoh: InfinityFree), struktur direktori untuk keamanan memisahkan document root (folder yang bisa diakses web) dari file inti aplikasi.

Struktur Direktori Tipikal:

/htdocs/               <-- Document Root (Akses Web)
|-- uploads/           <-- Folder tujuan file upload kita
|-- index.php
|-- laravel_core/      <-- Direktori inti Laravel (Tidak bisa diakses web)
|   |-- app/
|   |-- public/        <-- Folder public asli Laravel (TIDAK BISA DIAKSES)
|   |-- ...

Masalahnya adalah helper public_path() dari Laravel akan selalu menunjuk ke /htdocs/laravel_core/public/, bukan ke /htdocs/. Ini menyebabkan semua file yang diunggah tidak dapat diakses melalui browser, sehingga menghasilkan Error 404 Not Found.


2. Perbandingan Tiga Metode Solusi

Pendekatan #1: Menggunakan $_SERVER['DOCUMENT_ROOT']

Ini adalah solusi paling langsung yang menggunakan variabel superglobal PHP untuk mendapatkan path absolut ke htdocs.

  • Kelebihan:

    • Sederhana & Lugas: Logikanya sangat mudah dipahami.
    • Andal di Lingkungan Target: $_SERVER['DOCUMENT_ROOT'] hampir selalu diatur dengan benar oleh server web seperti Apache.
  • Kekurangan:

    • Tidak Portabel: Kode ini akan rusak di lingkungan development lokal standar, karena DOCUMENT_ROOT di lokal biasanya menunjuk ke .../project/public.
    • Bukan "The Laravel Way": Menggunakan variabel superglobal secara langsung menghindari abstraksi yang disediakan oleh framework, membuat kode lebih sulit diuji (testing).

Contoh Kode ($_SERVER['DOCUMENT_ROOT'])

// Di dalam Controller
public function uploadFile(Request $request)
{
    if ($request->hasFile('avatar')) {
        $documentRoot = $_SERVER['DOCUMENT_ROOT'];
        $imageName = time() . '.' . $request->avatar->extension();
        $destinationPath = $documentRoot . '/uploads/avatars'; // Menargetkan /htdocs/uploads/avatars
        $request->avatar->move($destinationPath, $imageName);
        $user->avatar = '/uploads/avatars/' . $imageName; // Simpan path relatif URL
        $user->save();
    }
}

Pendekatan #2: Menggunakan dirname(base_path())

Pendekatan ini memanfaatkan helper base_path() dari Laravel untuk menemukan direktori induknya, yang dalam kasus shared hosting ini adalah htdocs.

  • Kelebihan:

    • Lebih Konsisten: Bergantung pada struktur internal Laravel, bukan pada konfigurasi server web.
    • Tanpa Variabel Global: Menghindari penggunaan langsung $_SERVER, yang dianggap praktik lebih bersih.
  • Kekurangan:

    • Juga Tidak Portabel: Sama seperti Pendekatan #1, logika ini akan gagal total di lingkungan development lokal, karena dirname(base_path()) akan menunjuk ke direktori di atas folder proyek Anda.

Contoh Kode (dirname(base_path()))

// Di dalam Controller
public function uploadFile(Request $request)
{
    if ($request->hasFile('avatar')) {
        // base_path() -> /htdocs/laravel_core
        // dirname(base_path()) -> /htdocs
        $realPublicPath = dirname(base_path()); 
        
        $imageName = time() . '.' . $request->avatar->extension();
        $destinationPath = $realPublicPath . '/uploads/avatars';
        $request->avatar->move($destinationPath, $imageName);
        $user->avatar = '/uploads/avatars/' . $imageName;
        $user->save();
    }
}

Pendekatan #3: Deteksi Lingkungan Otomatis (Solusi Paling Direkomendasikan)

Ini adalah solusi paling tangguh dan profesional. Kode ini menggabungkan kekuatan public_path() (untuk lokal) dan dirname(base_path()) (untuk produksi) dengan membuatnya mampu mendeteksi di mana ia sedang berjalan.

  • Kelebihan:

    • Sangat Portabel: Satu basis kode bekerja secara konsisten di lingkungan lokal dan produksi tanpa perlu diubah.
    • Sesuai Praktik Terbaik Laravel: Menggunakan helper inti Laravel dan logika kondisional berdasarkan environment.
    • Dapat Diandalkan & Mudah Dipelihara: Tidak ada lagi kejutan saat melakukan deployment.
  • Kekurangan:

    • Memerlukan disiplin untuk memastikan variabel APP_ENV di file .env diatur dengan benar (local untuk lokal, production untuk server).

Langkah 1: Konfigurasi .env

  • Di file .env lokal Anda: APP_ENV=local
  • Di file .env server produksi: APP_ENV=production

Pastikan variabel APP_ENV diatur dengan benar di setiap lingkungan agar logika deteksi di atas berjalan.

Langkah 2: Implementasi Kode Universal di Controller

// File: app/Http/Controllers/PrototypeController.php

public function store(Request $request)
{
    // ... (Validasi request) ...

    // Deteksi lingkungan untuk menentukan path upload yang benar.
    $realPublicPath = app()->environment('production')
        ? dirname(base_path())      // Untuk server shared hosting
        : public_path();            // Untuk development lokal

    // Handle upload (contoh: cover_image)
    if ($request->hasFile('cover_image')) {
        $image = $request->file('cover_image');
        $imageName = time() . '.' . $image->getClientOriginalExtension();
        
        // Gunakan path yang benar sesuai lingkungan
        $image->move($realPublicPath . '/uploads/prototypes', $imageName);
        
        // Simpan path RELATIF ke database (selalu sama)
        $validatedData['cover_image_path'] = 'uploads/prototypes/' . $imageName;
    }
    
    // ...
    Prototype::create($validatedData);
    // ...
}

// Logika yang sama diterapkan untuk metode update() dan destroy() saat mengakses file.

Langkah 3. Menampilkan Gambar di View (Blade)

Dengan pendekatan ini, logika di sisi view tetap sangat bersih dan standar, karena path yang tersimpan di database selalu relatif terhadap document root yang benar.

@if($prototype->cover_image_path)
    {{-- Cek apakah path adalah URL eksternal atau file lokal --}}
    @if(Illuminate\Support\Str::startsWith($prototype->cover_image_path, 'http'))
        <img src="{{ $prototype->cover_image_path }}" alt="Cover Image">
    @else
        {{-- Helper 'asset()' akan bekerja sempurna di kedua lingkungan --}}
        <img src="{{ asset($prototype->cover_image_path) }}" alt="Cover Image">
    @endif
@endif

Kesimpulan

Metode Portabilitas (Lokal & Produksi) Kepatuhan Laravel Rekomendasi
$_SERVER['DOCUMENT_ROOT'] Rendah (Hanya Produksi) Rendah Untuk perbaikan cepat & sementara.
dirname(base_path()) Rendah (Hanya Produksi) Sedang Mirip dengan di atas, sedikit lebih bersih.
Deteksi Lingkungan Tinggi (Universal) Tinggi Sangat Direkomendasikan untuk semua proyek.

Untuk proyek BlackFile, Pendekatan #3 adalah standar yang harus diikuti untuk memastikan aplikasi dapat dikembangkan di lokal dan di-deploy ke shared hosting tanpa masalah.

Dengan mengikuti panduan ini, fungsionalitas upload file akan menjadi tangguh, aman, dan sepenuhnya portabel antara lingkungan development dan produksi.

Prompt

Buatkan saya method `store` dan `update` untuk sebuah `PrototypeController` di Laravel 10.

**Konteks & Aturan:**
1.  **Model:** `Prototype`
2.  **Upload File:** Ada dua jenis file yang bisa di-upload: `cover_image` dan `icon`.
3.  **Opsi Ganda:** Setiap file memiliki dua opsi input:
    * Upload file (dari input `name="cover_image"` dan `name="icon"`).
    * Link eksternal (dari input `name="cover_image_url"` dan `name="icon_url"`).
4.  **Prioritas:** Jika kedua opsi (file dan URL) diisi, prioritaskan **upload file**.
5.  **Path Portabel (Wajib):** Logika upload harus portabel antara environment `local` dan `production` (shared hosting).
    * Di **produksi**, path tujuan adalah `dirname(base_path())`.
    * Di **lokal**, path tujuan adalah `public_path()`.
    * Gunakan `app()->environment('production')` untuk deteksi.
6.  **Penyimpanan Database:** Path yang disimpan di database harus **relatif** (contoh: `uploads/prototypes/namafile.png`). Kolom di database bernama `cover_image_path` dan `icon_path`.
7.  **Logika Update:** Saat `update`, jika file lokal baru di-upload atau diganti dengan URL, file lokal yang lama harus **dihapus** dari server.

**Tugas:**
Buatkan kode PHP lengkap untuk `public function store(Request $request)` dan `public function update(Request $request, Prototype $prototype)` yang menerapkan semua aturan di atas, termasuk validasi, pemindahan file, penghapusan file lama (di `update`), dan me-return redirect dengan pesan sukses.

πŸ“¦ Source Code Penting

πŸ”§ Script Extract Vendor

Buat file extract.php di htdocs/ untuk ekstrak vendor yang sudah di-zip:

<?php
$zip = new ZipArchive;
$path = __DIR__ . '/laravel/vendor'; // direktori tujuan ekstrak
$namaZip = 'test.zip'; 

if (!is_dir($path)) {
    mkdir($path, 0755, true);
}

if ($zip->open($namaZip) === TRUE) {
    $zip->extractTo($path);
    $zip->close();
    echo "Vendor extracted to laravel/vendor successfully βœ….<br>";

    // βœ… Pastikan nama file yang dihapus sama dengan yang dibuka
    if (unlink($namaZip)) {
        echo "$namaZip deleted.";
    } else {
        echo "Failed to delete $namaZip.";
    }
} else {
    echo "Failed to open $namaZip.";
}
?>

Cara pakai:

  1. Zip folder vendor/ di lokal jadi vendor.zip
  2. Upload vendor.zip ke htdocs/
  3. Upload extract.php ke htdocs/
  4. Akses http://yourdomain.page.gd/extract.php
  5. Tunggu proses selesai
  6. Hapus extract.php untuk keamanan

πŸ”§ config/app.php - Provider & Alias

Daftarkan package PDF generator:

<?php

return [
    'providers' => [
        // Provider lainnya...
        Barryvdh\DomPDF\ServiceProvider::class,
    ],

    'aliases' => [
        // Alias lainnya...
        'Pdf' => Barryvdh\DomPDF\Facade\Pdf::class,
    ],
];

πŸ”§ app/Providers/AppServiceProvider.php - Path Configuration

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register()
    {
        // Perbaiki path public untuk InfinityFree
        $this->app->bind('path.public', function () {
            return base_path('../');
        });
    }

    /**
     * Bootstrap any application services.
     */
    public function boot()
    {
        // Paksa HTTPS jika diperlukan (opsional)
        if (env('FORCE_HTTPS', false)) {
            \URL::forceScheme('https');
        }
    }
}

πŸ”§ app/Http/Controllers/ExampleController.php - PDF Export

Contoh controller untuk generate PDF tanpa error path:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\View; // βœ… Tambahkan ini
use Barryvdh\DomPDF\Facade\Pdf;
use Dompdf\Dompdf; // βœ… Tambahkan ini juga
use Dompdf\Options; // βœ… Ini penting buat konfigurasi DomPDF

class ExampleController extends Controller
{
    public function exportPdf(Request $request)
    {
        $data = $this->getFilteredQuery($request)->get();
        
        $totalPemasukan = $data->where('kategoriJenis.jenis', 'Pemasukan')->sum('nominal');
        $totalPengeluaran = $data->where('kategoriJenis.jenis', 'Pengeluaran')->sum('nominal');
        $saldoAkhir = $totalPemasukan - $totalPengeluaran;
        
        $filters = $request->only(['tanggal_mulai', 'tanggal_selesai']);

        // Render manual untuk menghindari error path
        $view = view('laporan.pdf', compact(
            'data',
            'totalPemasukan', 
            'totalPengeluaran',
            'saldoAkhir',
            'filters'
        ))->render();

        $pdf = Pdf::loadHtml($view);
        
        return $pdf->download('laporan-' . date('d-m-Y') . '.pdf');
    }

     // METHOD BARU UNTUK EXPORT PDF (OPSI KEDUA)
    public function exportPdf(Request $request)
    {
    // Ambil data
    $data = $this->getFilteredQuery($request)->get();
    $totalPemasukan = $data->where('kategoriJenis.jenis', 'Pemasukan')->sum('nominal');
    $totalPengeluaran = $data->where('kategoriJenis.jenis', 'Pengeluaran')->sum('nominal');
    $saldoAkhir = $totalPemasukan - $totalPengeluaran;
    $filters = $request->only(['tanggal_mulai', 'tanggal_selesai']);

    // Render HTML view ke string
    $html = View::make('tabungan.pdf', compact(
        'data',
        'totalPemasukan',
        'totalPengeluaran',
        'saldoAkhir',
        'filters'
    ))->render();

    // Setup DomPDF
    $options = new Options();
    $options->set('isRemoteEnabled', true); // aktifkan load asset dari URL
    $dompdf = new Dompdf($options);

    $dompdf->loadHtml($html);
    $dompdf->setPaper('A4', 'portrait');
    $dompdf->render();

    // Output sebagai file download
    return response($dompdf->output(), 200)
        ->header('Content-Type', 'application/pdf')
        ->header('Content-Disposition', 'attachment; filename="laporan-tabungan-' . date('d-m-Y') . '.pdf"');
}
}

πŸ”§ app/helpers.php - Custom Asset Helper

<?php

/**
 * Generate asset URL untuk InfinityFree hosting
 */
if (!function_exists('public_asset')) {
    function public_asset($path)
    {
        return url('/') . '/' . ltrim($path, '/');
    }
}

/**
 * Check if app in production
 */
if (!function_exists('is_production')) {
    function is_production()
    {
        return app()->environment('production');
    }
}

/**
 * Generate versioned asset untuk cache busting
 */
if (!function_exists('versioned_asset')) {
    function versioned_asset($path)
    {
        $timestamp = filemtime(public_path($path));
        return public_asset($path) . '?v=' . $timestamp;
    }
}

πŸ”§ composer.json - Autoload Configuration

{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "require": {
        "php": "^7.4|^8.0",
        "laravel/framework": "^8.75"
    },
    "autoload": {
        "files": [
            "app/helpers.php"
        ],
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        }
    },
    "scripts": {
        "post-autoload-dump": [
            "@php artisan package:discover --ansi"
        ]
    }
}

πŸ”§ resources/views/layouts/app.blade.php - Asset Loading

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    
    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Styles -->
    @if(is_production())
        <!-- Production: gunakan public_asset helper -->
        <link href="{{ public_asset('assets/css/app.css') }}" rel="stylesheet">
        <link href="{{ public_asset('assets/css/bootstrap.min.css') }}" rel="stylesheet">
    @else
        <!-- Development: gunakan asset helper biasa -->
        <link href="{{ asset('css/app.css') }}" rel="stylesheet">
        <link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet">
    @endif
</head>
<body>
    <div id="app">
        @yield('content')
    </div>

    <!-- Scripts -->
    @if(is_production())
        <script src="{{ public_asset('assets/js/app.js') }}"></script>
        <script src="{{ public_asset('assets/js/bootstrap.bundle.min.js') }}"></script>
    @else
        <script src="{{ asset('js/app.js') }}"></script>
        <script src="{{ asset('js/bootstrap.bundle.min.js') }}"></script>
    @endif
    
    @stack('scripts')
</body>
</html>

πŸ”§ routes/web.php - Testing Routes

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
*/

Route::get('/', function () {
    return view('welcome');
});

// Route untuk testing database connection
Route::get('/test-db', function () {
    try {
        \DB::connection()->getPdo();
        $dbName = \DB::connection()->getDatabaseName();
        return "βœ… Database connection successful!<br>Database: {$dbName}";
    } catch (\Exception $e) {
        return "❌ Database connection failed: " . $e->getMessage();
    }
});

// Route untuk testing file permissions
Route::get('/test-storage', function () {
    $storagePath = storage_path('app');
    $isWritable = is_writable($storagePath);
    
    return $isWritable 
        ? "βœ… Storage directory is writable" 
        : "❌ Storage directory is not writable";
});

// Route untuk testing helper functions
Route::get('/test-helpers', function () {
    return [
        'public_asset' => public_asset('css/app.css'),
        'is_production' => is_production(),
        'app_url' => config('app.url'),
        'public_path' => public_path(),
    ];
});

🎯 Tips & Optimasi

Performance

# Di lokal sebelum upload
php artisan config:cache
php artisan route:cache
php artisan view:cache
npm run production  # Jika pakai Laravel Mix

Security

  • πŸ”’ Selalu gunakan APP_DEBUG=false di production
  • πŸ”’ Jangan expose file .env
  • πŸ”’ Update Laravel ke versi terbaru
  • πŸ”’ Gunakan HTTPS jika tersedia

Upload Strategy

  • ⏰ Upload di jam 00:00-06:00 WIB
  • πŸ“‘ Gunakan koneksi kabel untuk stabilitas
  • πŸ“¦ Compress asset sebelum upload
  • πŸ’Ύ Backup sebelum deploy

❓ FAQ

Q: Berapa lama waktu upload ke InfinityFree? A: File Laravel dasar: 5-10 menit, Vendor lengkap: 30-60 menit

Q: Apakah bisa pakai Laravel 11? A: Ya, tapi perlu penyesuaian struktur folder bootstrap yang baru

Q: Bagaimana jika upload vendor gagal terus? A: Coba upload di jam sepi atau gunakan metode kompresi, atau gunakan script extract.php untuk file zip

Q: Bisa pakai database selain MySQL? A: InfinityFree hanya support MySQL/MariaDB

Q: Bagaimana cara update aplikasi? A: Upload file yang berubah saja, jangan upload ulang vendor jika tidak perlu

Q: Apakah file extract.php aman? A: Pastikan hapus file extract.php setelah digunakan untuk mencegah akses yang tidak diinginkan


πŸ“ž Dukungan

Jika mengalami kendala:

  1. Periksa Troubleshooting di atas
  2. Cek error log di cPanel InfinityFree
  3. Buat issue di repository ini
  4. Join komunitas Laravel Indonesia

Last Updated: [Date]

πŸ“„ Lisensi

Panduan ini gratis digunakan untuk keperluan edukasi dan komersial.


πŸ’‘ Pro Tip: Bookmark panduan ini untuk referensi deploy selanjutnya!


Dibuat dengan ❀️ untuk komunitas Laravel Indonesia

About

Panduan Mendeploy Aplikasi Laravel ke Infinity Free (Hosting)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •