简体   繁体   中英

Using PHP to upload images to a folder while saving descriptions to a database

The user decides to upload multiple images for their classified listing. What I need to have happen is to:

  1. Upload the images with descriptions for each of the images - SOLVED
  2. Save the images to a folder specified
  3. Save the image location to my MySQL database with descriptions - SOLVED
  4. Be able to call the images and descriptions in their classified listing in kind of a gallery setting

My table schema is set up like this (simplified):

ad_id | member_id | category | subcategory | ... | photo_0_href | photo_0_desc ... | etc.

Could someone walk me through the process? Thanks.

step3.php

<form action="upload.php" method="post" enctype="multipart/form-data">
<p>
<label for="file0">Filename: </label>
<input name="file[]" type="file" id="file0" size="20" />
</p>
<p>
<label for="file0desc">Description: </label>
<textarea rows="10" cols="30" id="file0desc" class="textarea"></textarea>
</p>
<p>
<label for="file1">Filename: </label>
<input name="file[]" type="file" id="file1" size="20" />
</p>
<p>
<label for="file1desc">Description: </label>
<textarea rows="10" cols="30" id="file1desc" class="textarea"></textarea>
</p>
<p>
<input id="submit" type="submit" name="submit" value="Continue to Step 4" />
</p>
</form>

upload.php

<?php

if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/pjpeg")
|| ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 1048600)) // less than 1MB
  {
  if ($_FILES["file"]["error"] > 0)
    {
    echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
    }
  else
    {
    header("Location: step4.php");
/*
    echo "Upload: " . $_FILES["file"]["name"] . "<br />";
    echo "Type: " . $_FILES["file"]["type"] . "<br />";
    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";

*/
    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $_FILES["file"]["name"]);
      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
      }
    }
  }
else
  {
  echo "Invalid file";
  }
?>

I know my upload.php isn't configured for multiple image files yet, but it's a start.

I worked out saving the descriptions to the database using $_SESSION['file_n_desc'] . I just need to figure out how to upload the darn images to the folder and then save the locations into the database.

I also need to have the image renamed to a random string (to prevent images being overwritten). I know I can do this with the rand() function.

1) Uploading files

When using array syntax for file inputs, the file index is the last key. $_FILES["file"]["name"] , for example, is an array of file names. To get the information for the i-th file, you'll need to access $_FILES["file"]["name"][$i] , $_FILES["file"]["size"][$i] &c.

2) Saving Images to a Folder

Some of the data in $_FILES (such as the name) comes from the client, and thus is not to be trusted (that is, verify first). In the case of the file name , you could start by using realpath to verify the target file pathname is safe, or use basename or pathinfo to extract the last component of the supplied name before assembling the target pathname.

3) Saving image info to DB

The (incomplete) database schema you give looks like you give each image two columns in the same table. Under the relational model, to-many relationships are modeled with a separate table:

CREATE TABLE images (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    `path` VARCHAR(256) NOT NULL,
    `description` TEXT,
    `member` INT UNSIGNED NOT NULL,
    FOREIGN KEY `member` REFERENCES members (`id`) ON DELETE CASCADE ON UPDATE CASCADE -- the image's owner
) Engine=InnoDB;

-- Note: this is a many-to-many relationship
CREATE TABLE ad_images (
    `ad` INT UNSIGNED NOT NULL,
    `image` INT UNSIGNED NOT NULL,
    FOREIGN KEY `ad` REFERENCES ads (`ad_id`) ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY `image` REFERENCES images (id) ON DELETE CASCADE ON UPDATE CASCADE,
    UNIQUE KEY (`ad`, `image`)
) Engine=InnoDB;

Otherwise, you're breaking the zero-one-infinity rule and wasting space when there are fewer than the maximum number of images.

Other

Note that you can use array syntax for the file description fields to make handling them easier. Name them "filedesc[]".

Rather than a long sequence of comparisons, use an array lookup or a pattern match.

function isImage($type) {
    static $imageTypes = array(
            'image/gif'=>1, 'image/jpeg'=>1, 'image/pjpeg'=>1, 'image/png'=>1,
        );
    return isset($imageTypes[$type]);
    /* OR */
    return preg_match('%^image/(?:p?jpeg|gif|png)%', $type);
    /* OR allow all images */
    return preg_match('%^image/%', $type);
}

if (isImage($_FILES["file"]["type"][$idx]) && ($_FILES["file"]["size"][$idx] < 1048600)) {

The file type is one of those client-supplied values. Safer would be to use fileinfo to get the image type.

$finfo = finfo_open(FILEINFO_MIME_TYPE);

if (isImage(finfo_file($finfo, $path)) && ($_FILES["file"]["size"][$idx] < 1048600)) {

Even that can be fooled if the file has a valid image header but the rest isn't valid. You can use an image library (such as GD or ImageMagick) to validate the file by checking whether you can successfully open the file as an image.

Not sure what errors you are having, as you seem to have the process pretty clear in your head. The first time I did this, i used this tutorial as a guide and everything worked out just fine: http://php.about.com/od/phpwithmysql/ss/Upload_file_sql.htm

Might I suggest storing the file in the database instead?? Less security issues that way!

PS: I got the feeling you are a beginner and I think I made a nice list at PHP Newbies: How to write good code

2) Save the images to a folder specified

move-uploaded-file should work. What comes to mind is that you have not set the right write permissions to move files. What kind of server are you using(Linux/Windows/MacOSX)? What does the function return as boolean(result)?

One other thing: I need to have the image renamed to a random string (to prevent images being overwritten). I know I can do this with the rand() function, but I forgot to mention it in my original post.

rand() is not really random and could give duplicate filenames. uniqid will be unique

Edit: I figured out saving the descriptions to the database using $_SESSION['file_n_desc']. I just need to figure out how to upload the darn things and then save the locations into the database.

When using session when you have not overridden session you are not using your MySQL database but the file-system . You could read this tutorial to for store session in a database . Standard this session gets purged when closing the browser (just for that session). Just try this code:

<?php

session_start();

echo "<p>";
if (isset($_SESSION['count'])) {
    echo $_SESSION['count']++;
} else {
    $_SESSION['count'] = 0;
    echo $_SESSION['count']++;
}
echo "</p>";

echo "<p>" . session_id() . "</p>";

Load that page couple of times, without closing your browser. You will get something that looks like:

0

rlu10shi6l390il130qinlt913


1

rlu10shi6l390il130qinlt913

But when you close browser that session_id has changed and your counter has reset and you get output that looks like.

0

fiakdijmmk38i40f39fm8u5mi4

1

fiakdijmmk38i40f39fm8u5mi4

Be able to call the images and descriptions in their classified listing in kind of a gallery setting

I used PDO (Good Read) to do (My)SQL. I did unit-testing(TDD) using phpunit . To access the database I used SQLite in memory mode(very nice to do SQL-testing) => new PDO('sqlite::memory:'); . I tried to follow Uncle Bob's three rules (Good Read).

Normally you split this in multiple files. Every class in a separate file. Every class should be tested in Isolation(loose coupling) in a separate file.

I used foreign key to map "Listing to Image". I think this example is tested pretty thoroughly, but right now it is really time for me to get some sleep, so I am not sure;).

<?php

function createDatabase() {
    $db = new PDO('sqlite::memory:');
    $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    return $db;
}

function createTables($db) {

// Listing table containing all listings.
    $db->exec(
<<<EOT
CREATE TABLE IF NOT EXISTS listing(
id INTEGER PRIMARY KEY, 
description TEXT NOT NULL UNIQUE)
EOT
    );

// Image table containg all images.
    $db->exec(
<<<EOT
CREATE TABLE IF NOT EXISTS image(
id INTEGER PRIMARY KEY, 
listing_id INTEGER,
URL TEXT NOT NULL UNIQUE,
description TEXT NOT NULL UNIQUE,
FOREIGN KEY (listing_id) REFERENCES listing(id))
EOT
    );
}

class Listing {
    private $db;
    private $id;
    public $description;

    /*private function __construct() {

    }*/

    private function __construct(PDO $db, $id, $description) {
        $this->db = $db;
        $this->id = $id;
        $this->description = $description;
    }

    public static function create(PDO $db, $description) {
        $stmt = $db->prepare(<<<EOT
INSERT OR IGNORE INTO listing(description)
VALUES (:description)
EOT
        );
        $stmt->execute(array(
            ":description" => $description
        ));

        if ($stmt->rowCount() !== 1) {
            return NULL;
        }

        return new Listing($db, $db->lastInsertId(), $description);
    }

    public static function get(PDO $db, $id) {
        $stmt       = $db->prepare("SELECT description FROM listing WHERE id = :id");
        $stmt->execute(array(
            ":id" => $id
        ));
        $row = $stmt->fetch();

        if ($row == null) {
            return null;
        }
        return new Listing($db, $id, $row['description']);
    }

    public function getImages() {
        return Image::getImages($this->db, $this);
    }

    public function save() {
            $stmt = $this->db->prepare(
<<<EOT
UPDATE listing SET description = :description WHERE id = :id
EOT
            );
            $stmt->execute(array(
                ":description"  => $this->description,
                ":id"           => $this->id
            ));
    }

    public function id() {
        return $this->id;
    }
}

class Image {
    private $pdo;
    public $URL;
    private $id;
    public $description;

    private function __construct(PDO $pdo, $URL, $description, Listing $listing, $id) {
        $this->pdo          = $pdo;
        $this->URL          = $URL;
        $this->description  = $description;
        $this->id           = $id;
    }

    public static function create(PDO $pdo, $URL, $description, Listing $listing) {
        $stmt = $pdo->prepare(
<<<EOT
INSERT OR IGNORE INTO image(URL, listing_id, description)
VALUES (:URL, :listing_id, :description)
EOT
        );

        $stmt->execute(array(
            ":URL"          => $URL,
            ":listing_id"   => $listing->id(),
            ":description"  => $description
        ));

        if ($stmt->rowCount() !== 1) {
            return NULL;
        }

        return new Image($pdo, $URL, $description, $listing, $pdo->lastInsertId());
    }

    public function id() {
        return $this->id;
    }

    public static function getImages(PDO $pdo, Listing $listing) {
        $result = array();

        $stmt = $pdo->prepare(
<<<EOT
SELECT * FROM image where listing_id = :listing_id
EOT
        );

        $stmt->execute(array(
            ":listing_id"   => $listing->id(),
        ));

        while($row = $stmt->fetch()) {
            //$result[] = array($row['URL'], $row['description']);
            $result[] = new Image($pdo, $row['URL'], $row['description'], $listing, $row['id']);
        }  

        return $result;
    }
}

class Test extends PHPUnit_Framework_TestCase {
    protected $db;

    protected function setUp() {
        $this->db = createDatabase();
        createTables($this->db);
    }

    public function testCreatingSingleListing() {
        $listing1 = Listing::create($this->db, "Listing 1");   
        $this->assertEquals(1, $listing1->id());
    }


    public function testCreatingMultipleListings() {
        $listing1 = Listing::create($this->db, "Listing 1");
        $listing1 = Listing::create($this->db, "Listing 2");   
        $this->assertEquals(2, $listing1->id());
    }

    public function testReturningListingReturnsNullWhenNonexistence() {
        $this->assertNull(Listing::get($this->db, 1));
    }

    public function testReturningCreatedListing() {
        $Listing1 = Listing::create($this->db, "Listing 1");   
        $this->assertEquals("Listing 1", Listing::get($this->db, 1)->description);
    }

    public function testSavingListing() {
        $listing1 = Listing::create($this->db, "Listing 1");
        $listing1->description = "new";
        $listing1->save();
        $this->assertEquals("new", Listing::get($this->db, 1)->description);
    }

    public function testListingHasNoImagesWhenJustCreated() {
        $listing1 = Listing::create($this->db, "Listing 1");
        $this->assertEquals(array(), $listing1->getImages());
    }

    public function testAddingImageToListing() {
        $listing1 = Listing::create($this->db, "Listing 1");
        $image1 = Image::create($this->db, "http://localhost:12343/dfdfx/45.png", "first image", $listing1); 
        $this->assertEquals(array($image1), $listing1->getImages());
    }

    public function testAddingImagesToListing() {
        $listing1 = Listing::create($this->db, "Listing 1");
        $image1 = Image::create($this->db, "http://localhost:12343/dfdfx/45.png", "first image", $listing1);
        $image2 = Image::create($this->db, "http://localhost:12343/df/46.png", "second image", $listing1);
        $this->assertEquals(array($image1, $image2), $listing1->getImages());
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM