You've learned the core of Fat Free Framework across this series: routing, templates, databases, configuration, and utilities. Now it's time for the honest conversation — when should you actually use F3, and when should you choose something else?
This final part covers practical advice for production: an honest comparison with Laravel and Symfony, performance tips, testing with F3's built-in tools, and a deployment checklist.
1. When to Use F3 (and When Not To)
F3 isn't the right tool for every job, but it excels in specific scenarios. Here's an honest assessment.
Use F3 When
- Building small-to-medium apps — blogs, internal tools, dashboards, REST APIs with under 50 routes
- Rapid prototyping — when you need to validate an idea in hours, not days
- Minimal dependencies matter — shared hosting, limited RAM, or containers with size constraints
- You want freedom — no enforced structure, use closures or controllers, your choice
- Team knows PHP — no need to learn Blade, Twig, or Artisan conventions
Don't Use F3 When
- Large enterprise with 20+ developers — you need enforced conventions and extensive tooling
- Need built-in queues, WebSockets, or admin panels — Laravel's Horizon, Echo, and Nova don't have F3 equivalents
- Package ecosystem is critical — if your project depends on specific Laravel packages, use Laravel
- Client/team expects a certain framework — sometimes the business decision outweighs the technical one
For greenfield projects with small teams who value simplicity, F3 is a legitimate choice. For projects with complex domain logic or many integrations, the time you save on setup may be lost reinventing what Laravel provides.
2. F3 vs Laravel vs Symfony
An honest, side-by-side comparison for decision-making:
| Aspect | F3 | Laravel | Symfony |
|---|---|---|---|
| Core size | Single file (~90kb) | Large (~10MB) | Very large (~30MB) |
| Boot time | Very fast | Moderate | Moderate-slow |
| Memory usage | Low | Moderate | Higher |
| Learning curve | Hours | Days-weeks | Weeks-months |
| Opinionation | Minimal | Strong | Very strong |
| Ecosystem | Small | Large | Large |
| ORM | SQL Mapper | Eloquent | Doctrine |
| Templates | F3/Twig/any | Blade | Twig |
| Best for | APIs, small apps | Full-stack, teams | Enterprise |
3. Performance Tuning
F3 is already fast, but here's how to optimize for production.
Essential Production Settings
// config/prod.ini
[globals]
DEBUG = 0
CACHE = true
TZ = UTC
HIGHLIGHT = false Cache Strategy
- Route caching — Cache static pages (about, contact) with TTL 3600+
- Query caching — Cache expensive lookups (categories, config data)
- Memory backend — Use APC or Memcached instead of filesystem
- Disable in development — Turn off CACHE while iterating
// Route caching example
$f3->route('GET /about', 'Page->about', 3600); // Cache 1 hour
$f3->route('GET /contact', 'Page->contact', 86400); // Cache 1 day
// Query caching example
$categories = $db->exec('SELECT * FROM categories', null, 3600); 4. Profiling & Debugging
Useful Debug Settings
// Development settings
$f3->set('DEBUG', 3); // Maximum verbosity
$f3->set('HIGHLIGHT', true); // Syntax-highlighted stack traces
$f3->set('HALT', false); // Don't stop on warnings SQL Query Logging
// After running queries, check the log
$db = $f3->get('DB');
echo '<pre>' . $db->log() . '</pre>';
// Or log to file
file_put_contents(
'logs/queries.log',
$db->log(),
FILE_APPEND
); Profiling Tools
- Xdebug — Step-through debugging, profiling, cache grind
- Blackfire — Production-safe profiling (commercial)
- Blackfire-like alternative — Use
microtime(true)around critical sections
// Simple profiling
$start = microtime(true);
// Your code here
$results = $db->exec('SELECT * FROM large_table');
$elapsed = round((microtime(true) - $start) * 1000, 2);
echo "Query took {$elapsed}ms"; 5. Error Handling & Logging
Proper error handling separates hobby projects from production applications.
Production Error Handler
$f3->set('ONERROR', function($f3) {
// Log the error
$logger = new \Log('logs/errors.log');
$code = $f3->get('ERROR.code');
$text = $f3->get('ERROR.text');
$trace = $f3->get('ERROR.trace');
$logLine = sprintf(
"[%s] [%d] %s in %s:%d\n",
date('Y-m-d H:i:s'),
$code,
$text,
$trace[0]['file'] ?? 'unknown',
$trace[0]['line'] ?? 0
);
$logger->write($logLine);
// Show user-friendly error page
http_response_code($code);
echo \Template::instance()->render('errors/' . $code . '.htm');
}); • Rotate logs daily to prevent disk filling
• Include timestamp, error code, message, and file location
• Set proper file permissions on log directory (not world-writable)
6. Testing with F3
F3 includes a built-in Test class for unit testing. It's simple but effective for verifying your code works.
Basic Testing
<?php
require 'vendor/autoload.php';
$f3 = \Base::instance();
$test = new \Test;
// Test a function exists
$test->expect(
is_callable('my_function'),
'my_function() is callable'
);
// Test return value
$result = my_function();
$test->expect(
$result === 42,
'my_function() returns 42'
);
// Display results
foreach ($test->results() as $r) {
echo ($r['status'] ? 'PASS' : 'FAIL') . ': ' . $r['text'] . "\n";
} Testing Routes with Mock Requests
F3 can simulate HTTP requests without a browser, making it easy to test routes:
// Suppress route output during test
$f3->set('QUIET', true);
// Simulate a GET request to /about
$f3->mock('GET /about');
// Check the response
$response = $f3->get('RESPONSE');
$test->expect(
strpos($response, 'About') !== false,
'About page contains "About"'
);
// Test a route with parameters
$f3->mock('GET /user/42');
$id = $f3->get('PARAMS.id');
$test->expect(
$id === '42',
'User ID parameter is captured correctly'
);
// Test POST with form data
$f3->mock('POST /login', ['email' => '[email protected]']);
$email = $f3->get('POST.email');
$test->expect(
$email === '[email protected]',
'POST data is accessible'
);
// Re-enable output
$f3->set('QUIET', false);
$f3->clear('ERROR'); Test class is great for:• Quick smoke tests
• Verifying route behavior
• Simple assertions without dependencies
Use PHPUnit when:
• You need mocking, data providers, or test suites
• Running CI/CD pipelines that expect PHPUnit output
• Testing complex class hierarchies
7. Security Checklist
Before deploying any F3 application, verify these security measures:
| Check | Action | Why |
|---|---|---|
| DEBUG | Set to 0 | Prevents stack trace leaks |
| SQL injection | Use parameterized queries only | Prevents data theft |
| XSS | Rely on auto-escaping (templates) | Prevents script injection |
| CSRF | Check POST with token or origin | Prevents forged requests |
| Passwords | Use password_hash()/password_verify() | Never md5 or sha1 |
| Sessions | Regenerate ID after login | Prevents session fixation |
| File uploads | Validate type and size | Prevents malicious uploads |
| HTTPS | Redirect all traffic to HTTPS | Encrypts data in transit |
// Session fixation prevention (after login)
session_regenerate_id(true);
$f3->set('SESSION.user_id', $user->id);
// CSRF protection example
$f3->route('POST /delete', function($f3) {
if ($f3->get('POST.csrf_token') !== $f3->get('SESSION.csrf_token')) {
$f3->error(403, 'Invalid CSRF token');
return;
}
// Safe to process delete
}); 8. Deployment Checklist
Use this checklist before deploying to production:
- ☐ DEBUG = 0 — No stack traces in production
- ☐ CACHE enabled — Use APC or Memcached if available
- ☐ HTTPS configured — SSL certificate installed and forced
- ☐ Session security — Secure, HttpOnly, SameSite cookies
- ☐ Error logging — Logs go to file, not browser
- ☐ File permissions — config files not world-readable, logs writable
- ☐ Database credentials — Not hardcoded, use environment variables
- ☐ Timezone set —
TZ = UTCor your local timezone - ☐ Unused routes removed — No debug or test endpoints exposed
- ☐ Web server configured — Apache or Nginx, not PHP built-in server
Sample Nginx Configuration
server {
listen 80;
server_name example.com;
root /var/www/myapp;
index index.php;
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\. {
deny all;
}
} Sample Apache Configuration (.htaccess)
RewriteEngine On
RewriteBase /
# Redirect to HTTPS (optional)
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# F3 front controller - send all requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L] Series Complete!
Congratulations — you've completed the Fat Free Framework tutorial series. Here's what you learned:
- Part 1 — Installation, Hello World, project structure
- Part 2 — Routing, parameters, controllers, filters
- Part 3 — Template engine, includes, data passing
- Part 4 — SQL Mapper, CRUD, relationships, raw SQL
- Part 5 — Configuration, caching, sessions, i18n, plugins
- Part 6 — Real-world tips, testing, deployment (you are here)
F3 won't be the right choice for every project, but it's a powerful tool to have in your belt. For small APIs, internal tools, and rapid prototypes, it's hard to beat a lightweight framework that ships fast and keeps things simple.
The best framework is the one that gets your project shipped. Now go build something.
GitHub: fatfree-core
Community: Stack Overflow, Matrix chat