Kotchasan Framework Documentation
File and Image Handling
File and Image Handling
Kotchasan Framework provides comprehensive file and image handling classes that cover all aspects from upload processing to secure storage and advanced image manipulation.
Table of Contents
- File Class - File and Directory Management
- UploadedFile Class - Upload File Handling
- Files Collection - File Collections
- Image Class - Image Processing
- Real-world Usage
- Best Practices
File Class - File and Directory Management
Directory Management
use Kotchasan\File;
// Create directories safely
$uploadDir = '/var/www/uploads/documents/';
if (File::makeDirectory($uploadDir, 0755)) {
echo "Directory ready for use";
} else {
echo "Cannot create directory";
}
// Create complex directory structures
$userUploadDir = '/var/www/uploads/users/' . $userId . '/documents/';
$imageUploadDir = '/var/www/uploads/users/' . $userId . '/images/';
$tempDir = '/var/www/temp/' . session_id() . '/';
$directories = [$userUploadDir, $imageUploadDir, $tempDir];
foreach ($directories as $dir) {
if (!File::makeDirectory($dir, 0755)) {
throw new Exception("Cannot create directory: {$dir}");
}
}
// Copy entire directories
$sourceTemplate = '/var/www/templates/user_template/';
$userCustomDir = '/var/www/uploads/users/' . $userId . '/custom/';
File::copyDirectory($sourceTemplate, $userCustomDir);
echo "Template files copied successfully";File Management
// Find files in the system
$documentFiles = [];
File::listFiles('/var/www/uploads/documents/', $documentFiles, ['pdf', 'doc', 'docx']);
echo "Found " . count($documentFiles) . " documents:\n";
foreach ($documentFiles as $file) {
echo "- " . basename($file) . " (" . File::ext($file) . ")\n";
}
// Find image files
$imageFiles = [];
File::listFiles('/var/www/uploads/images/', $imageFiles, ['jpg', 'jpeg', 'png', 'gif', 'webp']);
// Create file list with additional information
$fileList = [];
foreach ($imageFiles as $file) {
$fileInfo = [
'path' => $file,
'name' => basename($file),
'extension' => File::ext($file),
'size' => filesize($file),
'modified' => filemtime($file)
];
$fileList[] = $fileInfo;
}
// Sort by modification date
usort($fileList, function($a, $b) {
return $b['modified'] - $a['modified'];
});File Cleanup
// Delete old temp files
$tempDir = '/var/www/temp/';
$tempFiles = [];
File::listFiles($tempDir, $tempFiles);
$cutoffTime = time() - (24 60 60); // 24 hours ago
foreach ($tempFiles as $file) {
if (filemtime($file) < $cutoffTime) {
unlink($file);
echo "Deleted temp file: " . basename($file) . "\n";
}
}
// Remove empty directories
$userDirs = glob('/var/www/uploads/users/*', GLOB_ONLYDIR);
foreach ($userDirs as $dir) {
$files = [];
File::listFiles($dir . '/', $files);
if (empty($files)) {
File::removeDirectory($dir . '/', true);
echo "Removed empty directory: " . basename($dir) . "\n";
}
}
// Clean cache files
$cacheDir = '/var/www/cache/';
File::removeDirectory($cacheDir, false); // Remove only files in dir
echo "Cache cleanup completed";
*/UploadedFile Class - Upload File Handling
Upload File Validation
use Kotchasan\Http\UploadedFile;
// Assume we got UploadedFile from Request
$uploadedFile = $request->getUploadedFiles()['document'] ?? null;
if ($uploadedFile && $uploadedFile->hasUploadFile()) {
// Check for errors
if ($uploadedFile->hasError()) {
$error = $uploadedFile->getErrorMessage();
throw new Exception("Upload error: {$error}");
}
// Check file size
$maxSize = 5; // 1024 1024; // 5MB
if ($uploadedFile->getSize() > $maxSize) {
throw new Exception("File too large (max 5MB)");
}
// Check file type
$allowedTypes = ['pdf', 'doc', 'docx', 'txt'];
if (!$uploadedFile->validFileExt($allowedTypes)) {
throw new Exception("File type not allowed (allowed: " . implode(', ', $allowedTypes) . ")");
}
echo "File passed basic validation";
} else {
throw new Exception("No uploaded file found");
}File Storage
use Kotchasan\Database;
// Create secure filename
$originalName = $uploadedFile->getClientFilename();
$cleanName = $uploadedFile->getCleanFilename('_');
$extension = strtolower(pathinfo($cleanName, PATHINFO_EXTENSION));
// Generate unique filename
$uniqueName = date('Y-m-d_H-i-s') . '_' . uniqid() . '.' . $extension;
// Determine storage folder by type
$uploadPath = match($extension) {
'pdf', 'doc', 'docx' => '/var/www/uploads/documents/',
'jpg', 'jpeg', 'png', 'gif', 'webp' => '/var/www/uploads/images/',
'mp4', 'avi', 'mov' => '/var/www/uploads/videos/',
default => '/var/www/uploads/others/'
};
// Create date-based directory structure
$dateDir = $uploadPath . date('Y/m/');
if (!File::makeDirectory($dateDir, 0755)) {
throw new Exception("Cannot create storage directory");
}
$targetPath = $dateDir . $uniqueName;
try {
// Move file to destination
$uploadedFile->moveTo($targetPath);
// Save file data to database
$fileData = [
'original_name' => $originalName,
'stored_name' => $uniqueName,
'file_path' => $targetPath,
'file_size' => $uploadedFile->getSize(),
'mime_type' => $uploadedFile->getClientMediaType(),
'extension' => $extension,
'uploaded_at' => date('Y-m-d H:i:s'),
'user_id' => $_SESSION['user_id'] ?? null
];
Kotchasan\Database::create()
->insert('uploaded_files')
->values($fileData)
->execute();
echo "File uploaded successfully: {$uniqueName}";
} catch (Exception $e) {
throw new Exception("Cannot store file: " . $e->getMessage());
}File Backup and Copy
// Backup important files
$backupDir = '/var/www/backups/' . date('Y-m-d') . '/';
File::makeDirectory($backupDir, 0755);
try {
// Copy instead of move (keep original)
$backupPath = $backupDir . $uniqueName;
$uploadedFile->copyTo($backupPath);
// Then move to main location
$uploadedFile->moveTo($targetPath);
echo "File stored with backup successfully";
} catch (Exception $e) {
throw new Exception("Storage error: " . $e->getMessage());
}Files Collection - File Collections
Multiple File Handling
use Kotchasan\Files;
// Create Files collection
$files = new Files();
// Add files from $_FILES
if (isset($_FILES['documents'])) {
if (is_array($_FILES['documents']['name'])) {
// Multiple files
for ($i = 0; $i < count($_FILES['documents']['name']); $i++) {
$files->add(
'document_' . $i,
$_FILES['documents']['tmp_name'][$i],
$_FILES['documents']['name'][$i],
$_FILES['documents']['type'][$i],
$_FILES['documents']['size'][$i],
$_FILES['documents']['error'][$i]
);
}
} else {
// Single file
$files->add(
'document',
$_FILES['documents']['tmp_name'],
$_FILES['documents']['name'],
$_FILES['documents']['type'],
$_FILES['documents']['size'],
$_FILES['documents']['error']
);
}
}
// Process all files
$uploadResults = [];
$uploadErrors = [];
foreach ($files as $key => $uploadedFile) {
try {
// Check file
if (!$uploadedFile->hasUploadFile()) {
continue; // Skip files that don't exist
}
if ($uploadedFile->hasError()) {
$uploadErrors[] = $key . ': ' . $uploadedFile->getErrorMessage();
continue;
}
// Check file type
$allowedExts = ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png'];
if (!$uploadedFile->validFileExt($allowedExts)) {
$uploadErrors[] = $key . ': File type not allowed';
continue;
}
// Upload file
$result = $this->processFileUpload($uploadedFile);
$uploadResults[] = $result;
} catch (Exception $e) {
$uploadErrors[] = $key . ': ' . $e->getMessage();
}
}
// Show results
if (!empty($uploadResults)) {
echo "Successfully uploaded " . count($uploadResults) . " files\n";
}
if (!empty($uploadErrors)) {
echo "Errors occurred:\n";
foreach ($uploadErrors as $error) {
echo "- {$error}\n";
}
}Image Class - Image Processing
Image Resizing
use Kotchasan\Image;
// Set image quality
$quality = 8; // 5; // For JPEG and WebP
// Auto-resize images (maintain aspect ratio)
$sourceImage = '/var/www/uploads/original/photo.jpg';
$thumbnailDir = '/var/www/uploads/thumbnails/';
// Create various thumbnail sizes
$thumbnailSizes = [
'small' => 150,
'medium' => 300,
'large' => 600
];
foreach ($thumbnailSizes as $size => $width) {
$targetFile = $thumbnailDir . $size . '_photo.jpg';
if (Image::resize($sourceImage, $thumbnailDir, $size . '_photo.jpg', $width)) {
echo "Created {$size} thumbnail ({$width}px) successfully\n";
}
}
// Resize with specific height
$profileDir = '/var/www/uploads/profiles/';
$profileImage = $profileDir . 'profile.jpg';
// Create 200x200 profile image (center crop)
if (Image::crop($sourceImage, $profileImage, 200, 200, '', false)) {
echo "Created profile image successfully\n";
}
// Create banner with fit (no crop)
$bannerImage = $profileDir . 'banner.jpg';
if (Image::crop($sourceImage, $bannerImage, 800, 200, '', true)) {
echo "Created banner image successfully\n";
}Adding Watermarks
// Set font for watermark
Image::$fontPath = '/var/www/fonts/arial.ttf';
// Add watermark to images
$watermarkText = '© 2024 My Website';
// Resize with watermark
$watermarkedFile = '/var/www/uploads/watermarked/photo_wm.jpg';
if (Image::resize($sourceImage, '/var/www/uploads/watermarked/', 'photo_wm.jpg', 800, $watermarkText)) {
echo "Added watermark successfully\n";
}
// Crop with watermark
$thumbnailWm = '/var/www/uploads/thumbnails/thumb_wm.jpg';
if (Image::crop($sourceImage, $thumbnailWm, 300, 300, $watermarkText, false)) {
echo "Created thumbnail with watermark successfully\n";
}Advanced Image Processing
// Load image from Base64
$base64Data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...';
$allowedFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$imageInfo = Image::loadImageFromBase64($base64Data, $allowedFormats);
if ($imageInfo !== false) {
$imageResource = $imageInfo['resource'];
$mimeType = $imageInfo['mime'];
$extension = $imageInfo['extension'];
// Process image
$processedImage = Image::processImageResource($imageResource, 400, 300, 'Sample Watermark', true);
// Save image
$outputFile = '/var/www/uploads/processed/image.' . $extension;
if (Image::saveImageResource($processedImage, $outputFile)) {
echo "Processed Base64 image successfully\n";
}
// Destroy resources
imagedestroy($processedImage);
imagedestroy($imageResource);
}
// Batch image processing
$sourceDir = '/var/www/uploads/originals/';
$outputDir = '/var/www/uploads/processed/';
$imageFiles = [];
File::listFiles($sourceDir, $imageFiles, ['jpg', 'jpeg', 'png']);
foreach ($imageFiles as $imagePath) {
$filename = basename($imagePath);
$outputPath = $outputDir . $filename;
try {
// Load image
$imageResource = Image::loadImageResource($imagePath);
// Resize to 800px (maintain aspect ratio)
$processedImage = Image::processImageResource($imageResource, 800, 0, '© My Company', false);
// Save
Image::saveImageResource($processedImage, $outputPath);
// Destroy resources
imagedestroy($processedImage);
imagedestroy($imageResource);
echo "Processed {$filename} successfully\n";
} catch (Exception $e) {
echo "Cannot process {$filename}: " . $e->getMessage() . "\n";
}
}Real-world Usage
Complete File Upload System
use Kotchasan\Database;
class FileUploadManager
{
private $uploadConfig = [
'documents' => [
'path' => '/var/www/uploads/documents/',
'allowed_exts' => ['pdf', 'doc', 'docx', 'txt'],
'max_size' => 10 1024 1024, // 10MB
'watermark' => false
],
'images' => [
'path' => '/var/www/uploads/images/',
'allowed_exts' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
'max_size' => 5 1024 1024, // 5MB
'watermark' => true,
'thumbnails' => [150, 300, 600]
],
'videos' => [
'path' => '/var/www/uploads/videos/',
'allowed_exts' => ['mp4', 'avi', 'mov'],
'max_size' => 100 1024 1024, // 100MB
'watermark' => false
]
];
public function handleUpload($uploadedFile, $category, $userId)
{
if (!isset($this->uploadConfig[$category])) {
throw new Exception("File category not allowed: {$category}");
}
$config = $this->uploadConfig[$category];
// Validate file
$this->validateFile($uploadedFile, $config);
// Prepare storage path
$storagePath = $this->prepareStoragePath($config['path'], $userId);
// Generate filename
$fileName = $this->generateFileName($uploadedFile);
$fullPath = $storagePath . $fileName;
// Upload file
$uploadedFile->moveTo($fullPath);
// Additional processing
$result = $this->postProcessFile($fullPath, $config, $uploadedFile);
// Save to database
return $this->saveFileRecord($result, $category, $userId);
}
private function validateFile($uploadedFile, $config)
{
if (!$uploadedFile->hasUploadFile()) {
throw new Exception("No uploaded file found");
}
if ($uploadedFile->hasError()) {
throw new Exception("Upload error: " . $uploadedFile->getErrorMessage());
}
if ($uploadedFile->getSize() > $config['max_size']) {
$maxSizeMB = $config['max_size'] / (1024 * 1024);
throw new Exception("File too large (max {$maxSizeMB}MB)");
}
if (!$uploadedFile->validFileExt($config['allowed_exts'])) {
throw new Exception("File type not allowed");
}
}
private function prepareStoragePath($basePath, $userId)
{
$datePath = date('Y/m/d/');
$userPath = $basePath . $userId . '/' . $datePath;
if (!File::makeDirectory($userPath, 0755)) {
throw new Exception("Cannot create storage directory");
}
return $userPath;
}
private function generateFileName($uploadedFile)
{
$originalName = $uploadedFile->getClientFilename();
$cleanName = $uploadedFile->getCleanFilename('_');
$extension = strtolower(pathinfo($cleanName, PATHINFO_EXTENSION));
return date('Y-m-d_H-i-s') . '_' . uniqid() . '.' . $extension;
}
private function postProcessFile($filePath, $config, $uploadedFile)
{
$result = [
'original_path' => $filePath,
'file_size' => filesize($filePath)
];
// Process images
if (in_array(File::ext($filePath), ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
$result = array_merge($result, $this->processImage($filePath, $config));
}
return $result;
}
private function processImage($imagePath, $config)
{
$result = ['thumbnails' => []];
$baseDir = dirname($imagePath) . '/';
$baseName = pathinfo($imagePath, PATHINFO_FILENAME);
$extension = pathinfo($imagePath, PATHINFO_EXTENSION);
// Create thumbnails
if (isset($config['thumbnails'])) {
foreach ($config['thumbnails'] as $size) {
$thumbName = $baseName . '_thumb_' . $size . '.' . $extension;
$thumbPath = $baseDir . 'thumbnails/' . $thumbName;
File::makeDirectory(dirname($thumbPath) . '/', 0755);
$watermark = $config['watermark'] ? '© My Website' : '';
if (Image::resize($imagePath, dirname($thumbPath) . '/', $thumbName, $size, $watermark)) {
$result['thumbnails'][$size] = $thumbPath;
}
}
}
return $result;
}
private function saveFileRecord($fileData, $category, $userId)
{
$record = [
'user_id' => $userId,
'category' => $category,
'original_path' => $fileData['original_path'],
'file_size' => $fileData['file_size'],
'thumbnails' => isset($fileData['thumbnails']) ? json_encode($fileData['thumbnails']) : null,
'uploaded_at' => date('Y-m-d H:i:s')
];
$fileId = Kotchasan\Database::create()
->insert('uploaded_files')
->values($record)
->execute();
return array_merge($record, ['id' => $fileId]);
}
}Gallery Management System
class GalleryManager
{
public function createGallery($images, $albumId)
{
$galleryPath = '/var/www/uploads/gallery/' . $albumId . '/';
File::makeDirectory($galleryPath, 0755);
$processedImages = [];
foreach ($images as $uploadedImage) {
try {
$result = $this->processGalleryImage($uploadedImage, $galleryPath);
$processedImages[] = $result;
} catch (Exception $e) {
// Log error and skip problematic images
error_log("Gallery processing error: " . $e->getMessage());
}
}
return $processedImages;
}
private function processGalleryImage($uploadedImage, $galleryPath)
{
// Generate filename
$fileName = uniqid() . '.jpg';
$originalPath = $galleryPath . 'originals/' . $fileName;
// Create subdirectories
File::makeDirectory(dirname($originalPath) . '/', 0755);
File::makeDirectory($galleryPath . 'thumbnails/', 0755);
File::makeDirectory($galleryPath . 'display/', 0755);
// Save original file
$uploadedImage->moveTo($originalPath);
// Create display image (1200px max)
$displayPath = $galleryPath . 'display/' . $fileName;
Image::resize($originalPath, dirname($displayPath) . '/', basename($displayPath), 1200, '© Gallery 2024');
// Create thumbnail (300px)
$thumbnailPath = $galleryPath . 'thumbnails/' . $fileName;
Image::crop($originalPath, $thumbnailPath, 300, 300, '', false);
return [
'filename' => $fileName,
'original_path' => $originalPath,
'display_path' => $displayPath,
'thumbnail_path' => $thumbnailPath,
'file_size' => filesize($originalPath)
];
}
$outputSize = 120; // 0)
{
$galleryPath = '/var/www/uploads/gallery/' . $albumId . '/';
$thumbnailDir = $galleryPath . 'thumbnails/';
$thumbnails = [];
File::listFiles($thumbnailDir, $thumbnails, ['jpg', 'jpeg', 'png']);
if (count($thumbnails) < 4) {
throw new Exception("Need at least 4 images");
}
// Create 2x2 mosaic
$tileSize = $outputSize / 2;
$mosaic = imagecreatetruecolor($outputSize, $outputSize);
$positions = [
[0, 0], [$tileSize, 0],
[0, $tileSize], [$tileSize, $tileSize]
];
for ($i = 0; $i < 4 && $i < count($thumbnails); $i++) {
$thumbnail = Image::loadImageResource($thumbnails[$i]);
$resized = Image::processImageResource($thumbnail, $tileSize, $tileSize, '', false);
imagecopy($mosaic, $resized, $positions[$i][0], $positions[$i][1], 0, 0, $tileSize, $tileSize);
imagedestroy($resized);
imagedestroy($thumbnail);
}
$mosaicPath = $galleryPath . 'mosaic.jpg';
Image::saveImageResource($mosaic, $mosaicPath);
imagedestroy($mosaic);
return $mosaicPath;
}
}Best Practices
1. File Security
// ✅ Good - Strict file type validation
function validateFileSecurely($uploadedFile) {
// Check extension
$allowedExts = ['jpg', 'jpeg', 'png', 'pdf'];
if (!$uploadedFile->validFileExt($allowedExts)) {
throw new Exception("File type not allowed");
}
// Check MIME type
$allowedMimes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($uploadedFile->getClientMediaType(), $allowedMimes)) {
throw new Exception("MIME type not allowed");
}
// Check actual file content
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMime = $finfo->file($uploadedFile->getTempFileName());
if (!in_array($realMime, $allowedMimes)) {
throw new Exception("File content does not match declared type");
}
}
// ✅ Good - Generate secure filenames
function generateSecureFileName($uploadedFile) {
$extension = strtolower(pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION));
return hash('sha256', uniqid() . time() . $uploadedFile->getClientFilename()) . '.' . $extension;
}2. Error Handling
// ✅ Good - Proper exception handling
class FileHandler
{
public function handleUpload($uploadedFile)
{
try {
$this->validateFile($uploadedFile);
$path = $this->saveFile($uploadedFile);
$this->logSuccess($path);
return $path;
} catch (InvalidArgumentException $e) {
$this->logError("Validation error", $e);
throw new Exception("Invalid file: " . $e->getMessage());
} catch (RuntimeException $e) {
$this->logError("Runtime error", $e);
throw new Exception("Cannot store file");
} catch (Exception $e) {
$this->logError("Unexpected error", $e);
throw new Exception("Unexpected error occurred");
}
}
private function logError($type, $exception)
{
error_log("File upload {$type}: " . $exception->getMessage());
}
private function logSuccess($path)
{
error_log("File uploaded successfully: " . $path);
}
}3. Performance Optimization
// ✅ Good - Memory efficient usage
class OptimizedImageProcessor
{
public function processBatchImages($imagePaths, $outputDir)
{
foreach ($imagePaths as $imagePath) {
$this->processImageMemoryEfficient($imagePath, $outputDir);
}
}
private function processImageMemoryEfficient($imagePath, $outputDir)
{
try {
// Load image
$imageResource = Image::loadImageResource($imagePath);
// Process
$processed = Image::processImageResource($imageResource, 800, 0, '', false);
// Save
$outputPath = $outputDir . basename($imagePath);
Image::saveImageResource($processed, $outputPath);
} finally {
// Always destroy resources
if (isset($processed) && is_resource($processed)) {
imagedestroy($processed);
}
if (isset($imageResource) && is_resource($imageResource)) {
imagedestroy($imageResource);
}
}
}
}
// ✅ Good - Lazy loading for large file lists
class FileListManager
{
$limit = 5; // 0)
{
$allFiles = [];
File::listFiles($directory, $allFiles);
$totalFiles = count($allFiles);
$offset = ($page - 1) * $limit;
$pagedFiles = array_slice($allFiles, $offset, $limit);
return [
'files' => $pagedFiles,
'pagination' => [
'current_page' => $page,
'total_pages' => ceil($totalFiles / $limit),
'total_files' => $totalFiles
]
];
}
}4. Cleanup and Maintenance
// ✅ Good - Automated cleanup system
class FileMaintenance
{
public function cleanupExpiredFiles()
{
$tempDir = '/var/www/uploads/temp/';
$expiredTime = time() - (24 60 60); // 24 hours
$tempFiles = [];
File::listFiles($tempDir, $tempFiles);
$deletedCount = 0;
foreach ($tempFiles as $file) {
if (filemtime($file) < $expiredTime) {
if (unlink($file)) {
$deletedCount++;
}
}
}
return $deletedCount;
}
public function optimizeImageDirectory($directory)
{
$imageFiles = [];
File::listFiles($directory, $imageFiles, ['jpg', 'jpeg', 'png']);
$optimizedCount = 0;
foreach ($imageFiles as $imagePath) {
$originalSize = filesize($imagePath);
// Reduce quality if file is too large
if ($originalSize > 1024 * 1024) { // > 1MB
$quality = 7; // 0;
$backupPath = $imagePath . '.backup';
rename($imagePath, $backupPath);
if (Image::resize($backupPath, dirname($imagePath) . '/', basename($imagePath), 0, '')) {
$newSize = filesize($imagePath);
if ($newSize < $originalSize) {
unlink($backupPath);
$optimizedCount++;
} else {
rename($backupPath, $imagePath);
}
}
}
}
return $optimizedCount;
}
}File and image handling in Kotchasan Framework provides flexibility and high security. Use carefully and follow best practices for maximum performance and security.