0

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)));
  }
}
13
  • If you want to perform an HTTP file upload, then the Content-Type must not be application/x-www-form-urlencoded, but multipart/form-data Commented Feb 9, 2024 at 8:09
  • And how you are building your formattedData, 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 server Commented Feb 9, 2024 at 8:12
  • @CBroe I had initially tried with multipart/form-data, but each time it gave me the error: "Missing Image". That's why I tried application/x-www-form-urlencoded, which allowed the backend to receive the image. However, maybe I used multipart/form-data incorrectly. I have just modified the code so that you can see what I did with formData. Commented Feb 9, 2024 at 8:46
  • "That's why I tried application/x-www-form-urlencoded, which allowed the backend to receive the image." - not sure what you mean by that. PHP will absolutely not populate $_FILES with anything, when you send application/x-www-form-urlencoded. Commented Feb 9, 2024 at 8:57
  • Remove the Content-Type header from your fetch options 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. Commented Feb 9, 2024 at 8:59

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.