I have developed a website using React and PHP. Currently, I am in the process of creating a React Native application that is similar to the website. For this purpose, I am using the same PHP for both the website and the application. React Native is still new to me, and I am stuck at a certain point. I would like to know how to send an image to the backend so that the backend can store the image and send the information to the database. Currently, when I send a new product, it says :
string(175) "file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FAppWok-db71a03d-1bff-412f-b36b-6ba8c457a16b/ImagePicker/30108c4a-2441-442f-aef3-87ce5be33537.jpeg"
Warning: mime_content_type(file: ///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FAppWok-db71a03d-1bff-412f-b36b-6ba8c457a16b/ImagePicker/30108c4a-2441-442f-aef3-87ce5be33537.jpeg): failed to open stream: No such file or directory in /var/www/html/controllers/foodController.php on line 31
Warning: file_get_contents(file: ///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FAppWok-db71a03d-1bff-412f-b36b-6ba8c457a16b/ImagePicker/30108c4a-2441-442f-aef3-87ce5be33537.jpeg): failed to open stream: No such file or directory in /var/www/html/controllers/foodController.php on line 34
{
"message": "Produit ajouté avec succès."
}
Frontend :
file ApiService :
addFood: async (foodData) => {
try {
const response = await fetch(`${BASE_URL}/foods/add`, {
method: 'POST',
body: foodData
});
const responseData = await response.json();
if (!response.ok) {
throw new Error(responseData.message || `HTTP error! Status: ${response.status}`);
}
return responseData;
} catch (error) {
throw error;
}
},
File where the upload form is located:
import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Image, ScrollView } from "react-native";
import { apiService } from "../API/ApiService";
import * as ImagePicker from 'expo-image-picker';
import RNPickerSelect from 'react-native-picker-select';
import Ionicons from "react-native-vector-icons/Ionicons";
import * as ImageManipulator from 'expo-image-manipulator';
import img from "../../assets/logo.png"
function FormAddProduct() {
const [productData, setProductData] = useState({
title: "",
description: "",
price: "",
imageURI: null,
category: "",
});
const [categorys, setCategorys] = useState([]);
useEffect(() => {
const fetchCategories = async () => {
try {
const fetchedCategories = await apiService.getAllCategories();
setCategorys(fetchedCategories);
} catch (error) {
console.error('Erreur lors de la récupération des catégories:', error);
}
};
fetchCategories();
}, []);
const handleSubmit = async () => {
try {
const formData = new FormData();
formData.append('title', productData.title);
formData.append('description', productData.description);
formData.append('category', productData.category);
formData.append('price', productData.price);
formData.append('image', {
uri: productData.imageURI,
type: "image/jpg",
name: `product-image-${Date.now()}.jpg`,
});
console.log("uri", productData.imageURI);
const result = await apiService.addFood(formData);
alert('Plat ajouté avec succès!');
resetForm();
} catch (error) {
console.error(error);
alert('Une erreur est survenue lors de l\'ajout du plat.');
}
};
const resetForm = () => {
setProductData({
title: "",
description: "",
price: "",
imageURI: null,
category: "",
});
};
const handlePriceChange = (inputValue) => {
const convertedValue = inputValue.replace(',', '.');
const regex = /^[0-9]*\.?[0-9]*$/;
if (regex.test(convertedValue) || convertedValue === '') {
setProductData({ ...productData, price: convertedValue });
}
};
const handleImageChange = async (result) => {
if (result && result.uri) {
try {
setProductData({ ...productData, imageURI: result.uri }); // Stocker l'URI dans productData.imageURI
console.log('reussi', productData.imageURI); // Afficher productData.imageURI dans le console.log
} catch (error) {
console.error('Erreur lors de la manipulation de l\'image:', error);
setProductData({ ...productData, imageURI: null });
}
} else {
setProductData({ ...productData, imageURI: null });
}
};
const pickFile = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
alert('Permission refusée pour accéder aux fichiers.');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
quality: 1,
});
if (!result.cancelled && result.assets.length > 0) {
handleImageChange(result.assets[0]); // Pass the first asset object
}
};
return (
<ScrollView style={styles.containerScrollAddProduct}>
<View style={styles.containerFormAddProduct}>
<TextInput
style={styles.inputAddProduct}
placeholder="Nom du produit"
value={productData.title}
onChangeText={(text) => setProductData({ ...productData, title: text })}
/>
<TextInput
style={styles.inputAddProduct}
placeholder="Description"
value={productData.description}
onChangeText={(text) => setProductData({ ...productData, description: text })}
/>
<View style={styles.containerSelectAddForm}>
<RNPickerSelect
items={categorys.map(category => ({ label: category.name, value: category.name }))}
onValueChange={(value) => setProductData({ ...productData, category: value })}
style={{ inputIOS: styles.picker, inputAndroid: styles.picker }}
useNativeAndroidPickerStyle={false}
Icon={() => {
return <Ionicons name="chevron-down" margin={11} size={30} color="#FF9A00" />;
}}
/>
</View>
<TextInput
style={styles.inputAddProduct}
placeholder="Prix"
value={productData.price}
onChangeText={handlePriceChange}
/>
<TouchableOpacity style={styles.buttonImageAddProduct} onPress={pickFile}><Text style={styles.textAddImage}>Choisir une image</Text></TouchableOpacity>
<TouchableOpacity style={styles.buttonAddProduct} onPress={handleSubmit}><Text style={styles.textAddProduct}>Ajouter</Text></TouchableOpacity>
<View>
<View style={styles.card}>
<Image source={productData.image && productData.imageURI ? { uri: productData.imageURI } : img} style={styles.imageCard}/>
<Text style={styles.textCard}>{productData.title}</Text>
<View style={styles.containerBottomCard}>
<Text style={styles.priceCard}>{productData.price} €</Text>
<View style={styles.containerButtonCard}>
<TouchableOpacity style={styles.buttonCard}><Text style={styles.textButtonCard}>+</Text></TouchableOpacity>
</View>
</View>
</View>
</View>
</View>
</ScrollView>
)
}
Backend :
public function uploadImageFromReactNative($imageURI)
{
$dossierDestination = "images/";
$imagePath = parse_url($imageURI, PHP_URL_PATH);
$nomFichier = basename($imagePath);
$imageContent = file_get_contents($imagePath);
$nomUnique = uniqid() . "." . pathinfo($nomFichier, PATHINFO_EXTENSION);
file_put_contents($dossierDestination . $nomUnique, $imageContent);
return $nomUnique;
}
public function addFood()
{
if (isset($_POST['title']) && isset($_POST['description']) && isset($_POST['category']) && isset($_POST['price'])) {
$title = $_POST['title'];
$description = $_POST['description'];
$category = $_POST['category'];
$price = $_POST['price'];
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
var_dump('if ',$_FILES['image']);
$imageFile = $_FILES['image'];
$imagePath = 'images/' . basename($imageFile['name']);
move_uploaded_file($imageFile['tmp_name'], $imagePath);
} elseif (isset($_POST['imageURI'])) {
var_dump('elseif ',$_POST['imageURI']);
$imagePath = $this->uploadImageFromReactNative($_POST['imageURI']);
} else {
http_response_code(400);
var_dump('elseimageUir ', $_POST['imageURI']);
var_dump('else image ', $_FILES['image']);
echo json_encode(array("message" => "Image manquante."));
return;
}
if ($this->model->addFood($title, $description, $category, $price, $imagePath)) {
http_response_code(201); // Code de succès pour création
echo json_encode(array("message" => "Produit ajouté avec succès."));
} else {
http_response_code(500); // Erreur interne du serveur
echo json_encode(array("message" => "Impossible d'ajouter le produit."));
}
} else {
http_response_code(400);
$missingFields = [];
if (!isset($_POST['title'])) {
$missingFields[] = 'title';
}
if (!isset($_POST['description'])) {
$missingFields[] = 'description';
}
if (!isset($_POST['category'])) {
$missingFields[] = 'category';
}
if (!isset($_POST['price'])) {
$missingFields[] = 'price';
}
echo json_encode(array("message" => "Données manquantes. Veuillez fournir les champs suivants : " . implode(', ', $missingFields)));
}
}
application/x-www-form-urlencoded, butmultipart/form-dataformattedData, creating key=value pairs separated with&- that is also not going to work, for a multipart request. Check React.js, how to send a multipart/form-data to serverContent-Typeheader from yourfetchoptions altogether. This header needs to include the boundary value, that is used to separate the request body parts from each other. fetch will set this header by itself, correctly, when you pass a formdata instance that contains file uploads. If you set it yourself like this, then it is likely the boundary value won't get added at all.