An uploaded image is available temporarily through $_FILES. After validating the upload, open the temporary file in binary mode and bind it to a PDO placeholder using PDO::PARAM_LOB.
require 'config.php';
$photo=fopen(
$_FILES['file_up']['tmp_name'],
'rb'
);
$sql="INSERT INTO student_profile
(id,student,profile_photo)
VALUES
(:id,:student,:profile_photo)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindValue(
':student',
$student,
PDO::PARAM_STR
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
The database operation uses a prepared INSERT statement. Before reaching this code, however, the uploaded file must be checked for upload errors, size and actual file type.
The existing Plus2net example uses the student_profile table with these fields:
| Column | Purpose |
|---|---|
id | Student ID |
student | Student name |
profile_photo | Binary image data |
The MySQL BLOB data type stores binary data rather than ordinary text.
BLOB column stores a maximum of 65,535 bytes. Many modern images are larger than this. The sample below therefore limits uploads to 60,000 bytes. For larger images, consider MEDIUMBLOB or storing the file outside the database.A file upload form must use method="post" and enctype="multipart/form-data".
<form action="uploadck.php"
method="post"
enctype="multipart/form-data">
Name:
<input type="text" name="t1" maxlength="10" required>
Select one ID:
<select name="id" required>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
Upload JPEG image:
<input type="file"
name="file_up"
accept="image/jpeg"
required>
<input type="submit" value="Upload Image">
</form>
The browser-side accept attribute is only a convenience for the visitor. PHP must still validate the uploaded file on the server.
See the HTML file upload field tutorial for the form control itself.
After a successful upload, PHP stores information about the file in:
$_FILES['file_up']
Important entries include:
| Entry | Meaning |
|---|---|
name | Original filename supplied by the browser |
type | Client-supplied MIME information; do not trust it for validation |
tmp_name | Temporary server-side uploaded file |
error | PHP upload status code |
size | Uploaded size in bytes |
$_FILES['file_up']['type'] or the filename extension. Both originate from client-side information.For this sample, we will accept only JPEG images up to 60,000 bytes so the file fits in the existing MySQL BLOB column.
if(
!isset($_FILES['file_up']) ||
$_FILES['file_up']['error']!==UPLOAD_ERR_OK
){
exit('File upload failed.');
}
$max_bytes=60000;
if(
$_FILES['file_up']['size']<=0 ||
$_FILES['file_up']['size']>$max_bytes
){
exit('Image must be smaller than 60 KB.');
}
$tmp_name=$_FILES['file_up']['tmp_name'];
if(!is_uploaded_file($tmp_name)){
exit('Invalid uploaded file.');
}
$finfo=new finfo(
FILEINFO_MIME_TYPE
);
$mime=$finfo->file($tmp_name);
if($mime!=='image/jpeg'){
exit('Only JPEG images are allowed.');
}
$image_info=getimagesize($tmp_name);
if($image_info===false){
exit('Uploaded file is not a valid image.');
}
This performs several different checks instead of relying on the filename alone.
Validate the ordinary form values before preparing the INSERT.
$id=filter_input(
INPUT_POST,
'id',
FILTER_VALIDATE_INT,
[
'options' => [
'min_range' => 1
]
]
);
$student=trim(
$_POST['t1'] ?? ''
);
if($id===false || $id===null){
exit('Invalid student ID.');
}
if(
$student==='' ||
strlen($student)>10
){
exit('Student name is invalid.');
}
Then open the validated temporary image in binary mode:
$photo=fopen(
$tmp_name,
'rb'
);
if($photo===false){
exit('Unable to read uploaded image.');
}
Bind the stream as PDO::PARAM_LOB:
$sql="INSERT INTO student_profile
(id,student,profile_photo)
VALUES
(:id,:student,:profile_photo)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindValue(
':student',
$student,
PDO::PARAM_STR
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
This combines the form-value checks, PHP upload checks, MIME validation and PDO INSERT.
<?php
require 'config.php';
if(
$_SERVER['REQUEST_METHOD']!=='POST'
){
exit('Invalid request.');
}
$id=filter_input(
INPUT_POST,
'id',
FILTER_VALIDATE_INT,
[
'options' => [
'min_range' => 1
]
]
);
$student=trim(
$_POST['t1'] ?? ''
);
if($id===false || $id===null){
exit('Invalid student ID.');
}
if(
$student==='' ||
strlen($student)>10
){
exit('Student name is invalid.');
}
if(
!isset($_FILES['file_up']) ||
$_FILES['file_up']['error']!==UPLOAD_ERR_OK
){
exit('File upload failed.');
}
$max_bytes=60000;
if(
$_FILES['file_up']['size']<=0 ||
$_FILES['file_up']['size']>$max_bytes
){
exit('Image must be smaller than 60 KB.');
}
$tmp_name=$_FILES['file_up']['tmp_name'];
if(!is_uploaded_file($tmp_name)){
exit('Invalid uploaded file.');
}
$finfo=new finfo(
FILEINFO_MIME_TYPE
);
$mime=$finfo->file($tmp_name);
if($mime!=='image/jpeg'){
exit('Only JPEG images are allowed.');
}
if(getimagesize($tmp_name)===false){
exit('Uploaded file is not a valid image.');
}
$photo=null;
try{
$photo=fopen(
$tmp_name,
'rb'
);
if($photo===false){
throw new RuntimeException(
'Unable to open uploaded image.'
);
}
$sql="INSERT INTO student_profile
(id,student,profile_photo)
VALUES
(:id,:student,:profile_photo)";
$stmt=$dbo->prepare($sql);
$stmt->bindValue(
':id',
$id,
PDO::PARAM_INT
);
$stmt->bindValue(
':student',
$student,
PDO::PARAM_STR
);
$stmt->bindParam(
':profile_photo',
$photo,
PDO::PARAM_LOB
);
$stmt->execute();
echo 'Student data and image were added.';
}catch(Throwable $e){
error_log(
'Image upload error: '.
$e->getMessage()
);
echo 'Unable to save the uploaded image.';
}finally{
if(is_resource($photo)){
fclose($photo);
}
}
MySQL provides several binary large-object types with different maximum sizes:
| Type | Approximate maximum |
|---|---|
TINYBLOB | 255 bytes |
BLOB | 65,535 bytes |
MEDIUMBLOB | 16 MB |
LONGBLOB | 4 GB |
A normal photograph can easily exceed the capacity of a basic BLOB column. If the application is intended to store larger profile images, use an appropriately sized column such as MEDIUMBLOB after considering database and application limits.
The server must also be able to accept and process the file size. Increasing the database column size alone does not change PHP's upload limits.
The current sample table stores only the binary image. That works when the application already knows the content type, such as this JPEG-only example.
If different image formats are accepted, store the detected MIME type with the binary data:
CREATE TABLE student_profile_new (
id INT PRIMARY KEY,
student VARCHAR(50) NOT NULL,
profile_photo MEDIUMBLOB NOT NULL,
mime_type VARCHAR(50) NOT NULL
);
Then validate against a strict server-side allowlist:
$allowed_mimes=[
'image/jpeg',
'image/png',
'image/webp'
];
if(
!in_array(
$mime,
$allowed_mimes,
true
)
){
exit('Unsupported image type.');
}
The saved MIME type can later be used when the BLOB is returned to the browser. We will cover that in PDO BLOB display and update.
Always check the upload error value before reading tmp_name.
| Constant | Meaning |
|---|---|
UPLOAD_ERR_OK | Upload completed successfully |
UPLOAD_ERR_INI_SIZE | File exceeded PHP's upload_max_filesize |
UPLOAD_ERR_FORM_SIZE | File exceeded a form-defined limit |
UPLOAD_ERR_PARTIAL | Only part of the file was uploaded |
UPLOAD_ERR_NO_FILE | No file was uploaded |
UPLOAD_ERR_NO_TMP_DIR | Temporary upload directory is missing |
UPLOAD_ERR_CANT_WRITE | PHP could not write the temporary file |
UPLOAD_ERR_EXTENSION | A PHP extension stopped the upload |
PHP configuration can reject an upload before the application code processes the file.
Common settings include:
upload_max_filesize = 2M
post_max_size = 8M
post_max_size must be large enough for the complete POST request, including the uploaded file and other form fields.
If a POST request exceeds post_max_size, PHP can provide an empty or incomplete $_POST/$_FILES request, so an application should not assume that a missing file always means the visitor forgot to choose one.
Storing images in the database can be appropriate when the binary data needs to remain closely associated with database records, database permissions or transactional workflows.
However, it is not the only design:
For a small tutorial and compact binary data, BLOB storage is useful for learning PDO LOB handling. For large files or large-scale media delivery, storing the binary file outside the database is often more practical.
Check $_FILES['file_up']['error'] first. A temporary filename should not be assumed to represent a successful upload.
A file named photo.jpg is not necessarily a JPEG. Detect the content type from the temporary file on the server.
The browser supplies this value. Use server-side MIME detection such as finfo for validation.
A basic MySQL BLOB is limited to about 64 KB. Larger images require a larger binary column or a different file-storage strategy.
File uploads require:
enctype="multipart/form-data"
Log database errors on the server rather than printing errorInfo() or raw exception messages to visitors.
Prepared statements protect SQL parameter handling. They do not verify MIME type, image validity, upload size or application permissions.
A single independent INSERT normally does not require an explicit PDO transaction. Transactions become useful when the upload must succeed together with other dependent database changes.
Validate the PHP upload, open the temporary file in binary mode, prepare an INSERT statement and bind the file stream to a placeholder using PDO::PARAM_LOB.
multipart/form-data allows the browser to send binary file content as part of the POST request. Without it, the uploaded file will not be available normally through $_FILES.
No. Validate the temporary file on the server using MIME detection and, for images, an image-validation function such as getimagesize().
A MySQL BLOB stores up to 65,535 bytes. Larger binary data may require MEDIUMBLOB, LONGBLOB or an external file-storage approach.
PDO::PARAM_LOB tells PDO that the bound value represents large-object or binary data. A file stream can be bound to the BLOB placeholder using this parameter type.
If the application accepts different binary or image formats, storing the server-detected MIME type allows the correct Content-Type to be sent when the BLOB is retrieved.
No. BLOB storage can be useful when binary data belongs closely with database records, while file-system or object storage is often more practical for large files and high-volume media delivery.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.