From e38f359b1dd2782e404774ead1d1074a55f82f50 Mon Sep 17 00:00:00 2001
From: Bram Goffings <bramgoffings@gmail.com>
Date: Wed, 23 May 2012 20:18:26 +0200
Subject: [PATCH] file entity

---
 core/includes/file.inc                             |  235 ++++++++------------
 core/includes/gettext.inc                          |   28 ++-
 core/lib/Drupal/Core/File/File.php                 |   94 ++++++++
 .../lib/Drupal/Core/File/FileStorageController.php |   51 +++++
 .../entity/tests/entity_crud_hook_test.test        |   10 +-
 core/modules/file/file.field.inc                   |    4 +-
 core/modules/file/file.module                      |   39 ++--
 core/modules/file/tests/file.test                  |    4 +-
 core/modules/image/image.module                    |   10 +-
 core/modules/image/image.test                      |    4 +-
 core/modules/locale/locale.bulk.inc                |    2 +-
 core/modules/system/system.api.php                 |   69 +++---
 core/modules/system/system.module                  |    4 +-
 core/modules/system/system.test                    |    3 +-
 core/modules/system/tests/file.test                |   83 ++++----
 core/modules/system/tests/image.test               |    2 +-
 .../tests/modules/file_test/file_test.module       |   27 ++-
 .../user/lib/Drupal/user/UserStorageController.php |    7 +-
 core/modules/user/user.module                      |    7 +-
 19 files changed, 388 insertions(+), 295 deletions(-)
 create mode 100644 core/lib/Drupal/Core/File/File.php
 create mode 100644 core/lib/Drupal/Core/File/FileStorageController.php

diff --git a/core/includes/file.inc b/core/includes/file.inc
index f204989..3af6a43 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\StreamWrapper\LocalStream;
+use Drupal\Core\File\File;
 
 /**
  * Stream wrapper bit flags that are the basis for composite types.
@@ -83,7 +84,7 @@ define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_N
  * @{
  * Common file handling functions.
  *
- * Fields on the file object:
+ * Fields on the file entity:
  * - fid: File ID
  * - uid: The {users}.uid of the user who is associated with the file.
  * - filename: Name of the file with no path components. This may differ from
@@ -574,7 +575,7 @@ function file_save_htaccess($directory, $private = TRUE) {
 }
 
 /**
- * Loads file objects from the database.
+ * Loads file entities from the database.
  *
  * @param array|bool $fids
  *   An array of file IDs, or FALSE to load all files.
@@ -586,7 +587,7 @@ function file_save_htaccess($directory, $private = TRUE) {
  *   loadable by this function.
  *
  * @return array
- *   An array of file objects, indexed by fid.
+ *   An array of file entities, indexed by fid.
  *
  * @todo Remove $conditions in Drupal 8.
  *
@@ -600,13 +601,13 @@ function file_load_multiple($fids = array(), array $conditions = array()) {
 }
 
 /**
- * Loads a single file object from the database.
+ * Loads a single file entity from the database.
  *
  * @param $fid
  *   A file ID.
  *
- * @return
- *   An object representing the file, or FALSE if the file was not found.
+ * @return Drupal\Core\File\File
+ *   A file entity or FALSE if the file was not found.
  *
  * @see hook_file_load()
  * @see file_load_multiple()
@@ -617,60 +618,10 @@ function file_load($fid) {
 }
 
 /**
- * Saves a file object to the database.
- *
- * If the $file->fid is not set a new record will be added.
- *
- * @param $file
- *   A file object returned by file_load().
- *
- * @return
- *   The updated file object.
- *
- * @see hook_file_insert()
- * @see hook_file_update()
- */
-function file_save(stdClass $file) {
-  $file->timestamp = REQUEST_TIME;
-  $file->filesize = filesize($file->uri);
-  if (!isset($file->langcode)) {
-    // Default the file's language code to none, because files are language
-    // neutral more often than language dependent. Until we have better flexible
-    // settings.
-    // @todo See http://drupal.org/node/258785 and followups.
-    $file->langcode = LANGUAGE_NOT_SPECIFIED;
-  }
-
-  // Load the stored entity, if any.
-  if (!empty($file->fid) && !isset($file->original)) {
-    $file->original = entity_load_unchanged('file', $file->fid);
-  }
-
-  module_invoke_all('file_presave', $file);
-  module_invoke_all('entity_presave', $file, 'file');
-
-  if (empty($file->fid)) {
-    drupal_write_record('file_managed', $file);
-    // Inform modules about the newly added file.
-    module_invoke_all('file_insert', $file);
-    module_invoke_all('entity_insert', $file, 'file');
-  }
-  else {
-    drupal_write_record('file_managed', $file, 'fid');
-    // Inform modules that the file has been updated.
-    module_invoke_all('file_update', $file);
-    module_invoke_all('entity_update', $file, 'file');
-  }
-
-  unset($file->original);
-  return $file;
-}
-
-/**
  * Determines where a file is used.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  *
  * @return
  *   A nested array with usage data. The first level is keyed by module name,
@@ -680,7 +631,7 @@ function file_save(stdClass $file) {
  * @see file_usage_add()
  * @see file_usage_delete()
  */
-function file_usage_list(stdClass $file) {
+function file_usage_list(File $file) {
   $result = db_select('file_usage', 'f')
     ->fields('f', array('module', 'type', 'id', 'count'))
     ->condition('fid', $file->fid)
@@ -703,8 +654,8 @@ function file_usage_list(stdClass $file) {
  * - The User module associates an image with a user, so $type would be 'user'
  *   and the $id would be the user's uid.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $module
  *   The name of the module using the file.
  * @param $type
@@ -717,7 +668,7 @@ function file_usage_list(stdClass $file) {
  * @see file_usage_list()
  * @see file_usage_delete()
  */
-function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
+function file_usage_add(File $file, $module, $type, $id, $count = 1) {
   db_merge('file_usage')
     ->key(array(
       'fid' => $file->fid,
@@ -732,15 +683,15 @@ function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
   // Make sure that a used file is permament.
   if ($file->status != FILE_STATUS_PERMANENT) {
     $file->status = FILE_STATUS_PERMANENT;
-    file_save($file);
+    $file->save();
   }
 }
 
 /**
  * Removes a record to indicate that a module is no longer using a file.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $module
  *   The name of the module using the file.
  * @param $type
@@ -756,9 +707,8 @@ function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) {
  *
  * @see file_usage_add()
  * @see file_usage_list()
- * @see file_delete()
  */
-function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) {
+function file_usage_delete(File $file, $module, $type = NULL, $id = NULL, $count = 1) {
   // Delete rows that have a exact or less value to prevent empty rows.
   $query = db_delete('file_usage')
     ->condition('module', $module)
@@ -792,7 +742,7 @@ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $c
   $usage = file_usage_list($file);
   if (empty($usage)) {
     $file->status = 0;
-    file_save($file);
+    $file->save();
   }
 }
 
@@ -811,8 +761,8 @@ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $c
  *   temporary file, the resulting file will also be a temporary file. See
  *   file_save_upload() for details on temporary files.
  *
- * @param $source
- *   A file object.
+ * @param Drupal\Core\File\File $source
+ *   A file entity.
  * @param $destination
  *   A string containing the destination that $source should be copied to.
  *   This must be a stream wrapper URI.
@@ -831,7 +781,7 @@ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $c
  * @see file_unmanaged_copy()
  * @see hook_file_copy()
  */
-function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
+function file_copy(File $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
   if (!file_valid_uri($destination)) {
     if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
       watchdog('file', 'File %file (%realpath) could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
@@ -863,7 +813,7 @@ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS
       $file->filename = drupal_basename($destination);
     }
 
-    $file = file_save($file);
+    $file->save();
 
     // Inform modules that the file has been copied.
     module_invoke_all('file_copy', $file, $source);
@@ -1058,8 +1008,8 @@ function file_destination($destination, $replace) {
  *   replace the file or rename the file based on the $replace parameter.
  * - Adds the new file to the files database.
  *
- * @param $source
- *   A file object.
+ * @param Drupal\Core\File\File $source
+ *   A file entity.
  * @param $destination
  *   A string containing the destination that $source should be moved to.
  *   This must be a stream wrapper URI.
@@ -1067,20 +1017,20 @@ function file_destination($destination, $replace) {
  *   Replace behavior when the destination file already exists:
  *   - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
  *       the destination name exists then its database entry will be updated and
- *       file_delete() called on the source file after hook_file_move is called.
+ *       $source->delete() called after invoking hook_file_move().
  *       If no database entry is found then the source files record will be
  *       updated.
  *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
  *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  *
- * @return
- *   Resulting file object for success, or FALSE in the event of an error.
+ * @return Drupal\Core\File\File
+ *   Resulting file entity for success, or FALSE in the event of an error.
  *
  * @see file_unmanaged_move()
  * @see hook_file_move()
  */
-function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
+function file_move(File $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) {
   if (!file_valid_uri($destination)) {
     if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
       watchdog('file', 'File %file (%realpath) could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
@@ -1112,14 +1062,14 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS
       $file->filename = drupal_basename($destination);
     }
 
-    $file = file_save($file);
+    $file->save();
 
     // Inform modules that the file has been moved.
     module_invoke_all('file_move', $file, $source);
 
     // Delete the original if it's not in use elsewhere.
     if ($delete_source && !file_usage_list($source)) {
-      file_delete($source);
+      $source->delete();
     }
 
     return $file;
@@ -1295,30 +1245,31 @@ function file_create_filename($basename, $directory) {
  * file usages instead. That will automatically mark the file as temporary and
  * remove it during cleanup.
  *
- * @param $file
- *   A file object.
+ * @param $fid
+ *   The file id.
  *
  * @see file_unmanaged_delete()
  * @see file_usage_list()
- * @see file_usage_delete()
- * @see hook_file_predelete()
- * @see hook_file_delete()
  */
-function file_delete(stdClass $file) {
-  // Let other modules clean up any references to the file prior to deletion.
-  module_invoke_all('file_predelete', $file);
-  module_invoke_all('entity_predelete', $file, 'file');
-
-  // Attempt to delete the file on the filesystem. Failures will be logged in
-  // watchdog.
-  file_unmanaged_delete($file->uri);
-
-  db_delete('file_managed')->condition('fid', $file->fid)->execute();
-  db_delete('file_usage')->condition('fid', $file->fid)->execute();
+function file_delete($fid) {
+  return file_delete_multiple(array($fid));
+}
 
-  // Let other modules respond to file deletion.
-  module_invoke_all('file_delete', $file);
-  module_invoke_all('entity_delete', $file, 'file');
+/**
+ * Deletes files.
+ *
+ * Instead of directly deleting a file, it is strongly recommended to delete
+ * file usages instead. That will automatically mark the file as temporary and
+ * remove it during cleanup.
+ *
+ * @param $fid
+ *   The file id.
+ *
+ * @see file_unmanaged_delete()
+ * @see file_usage_list()
+ */
+function file_delete_multiple(array $fids) {
+  entity_delete_multiple('file', $fids);
 }
 
 /**
@@ -1452,7 +1403,7 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) {
  *   in the event of an error, or NULL if no file was uploaded. The
  *   documentation for the "File interface" group, which you can find under
  *   Related topics, or the header at the top of this file, documents the
- *   components of a file object. In addition to the standard components,
+ *   components of a file entity. In addition to the standard components,
  *   this function adds:
  *   - source: Path to the file before it is moved.
  *   - destination: Path to the file after it is moved (same as 'uri').
@@ -1498,15 +1449,15 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
       drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source])), 'error');
       return FALSE;
   }
-
-  // Begin building file object.
-  $file = new stdClass();
-  $file->uid      = $user->uid;
-  $file->status   = 0;
-  $file->filename = trim(drupal_basename($_FILES['files']['name'][$source]), '.');
-  $file->uri      = $_FILES['files']['tmp_name'][$source];
+  // Begin building file entity.
+  $file = entity_create('file', array(
+    'uid' => $user->uid,
+    'status' => 0,
+    'filename' => trim(drupal_basename($_FILES['files']['name'][$source]), '.'),
+    'uri' => $_FILES['files']['tmp_name'][$source],
+    'filesize' => $_FILES['files']['size'][$source],
+  ));
   $file->filemime = file_get_mimetype($file->filename);
-  $file->filesize = $_FILES['files']['size'][$source];
 
   $extensions = '';
   if (isset($validators['file_validate_extensions'])) {
@@ -1618,12 +1569,10 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
   }
 
   // If we made it this far it's safe to record this file in the database.
-  if ($file = file_save($file)) {
-    // Add file to the cache.
-    $upload_cache[$source] = $file;
-    return $file;
-  }
-  return FALSE;
+  $file->save();
+  // Add file to the cache.
+  $upload_cache[$source] = $file;
+  return $file;
 }
 
 /**
@@ -1669,12 +1618,12 @@ function drupal_move_uploaded_file($filename, $uri) {
  * After executing the validator callbacks specified hook_file_validate() will
  * also be called to allow other modules to report errors about the file.
  *
- * @param $file
- *   A Drupal file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $validators
  *   An optional, associative array of callback functions used to validate the
  *   file. The keys are function names and the values arrays of callback
- *   parameters which will be passed in after the file object. The
+ *   parameters which will be passed in after the file entity. The
  *   functions should return an array of error messages; an empty array
  *   indicates that the file passed validation. The functions will be called in
  *   the order specified.
@@ -1684,7 +1633,7 @@ function drupal_move_uploaded_file($filename, $uri) {
  *
  * @see hook_file_validate()
  */
-function file_validate(stdClass &$file, $validators = array()) {
+function file_validate(File &$file, $validators = array()) {
   // Call the validation functions specified by this function's caller.
   $errors = array();
   foreach ($validators as $function => $args) {
@@ -1701,13 +1650,13 @@ function file_validate(stdClass &$file, $validators = array()) {
 /**
  * Checks for files with names longer than can be stored in the database.
  *
- * @param $file
- *   A Drupal file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  *
  * @return
  *   An array. If the file name is too long, it will contain an error message.
  */
-function file_validate_name_length(stdClass $file) {
+function file_validate_name_length(File $file) {
   $errors = array();
 
   if (empty($file->filename)) {
@@ -1722,8 +1671,8 @@ function file_validate_name_length(stdClass $file) {
 /**
  * Checks that the filename ends with an allowed extension.
  *
- * @param $file
- *   A Drupal file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $extensions
  *   A string with a space separated list of allowed extensions.
  *
@@ -1733,7 +1682,7 @@ function file_validate_name_length(stdClass $file) {
  *
  * @see hook_file_validate()
  */
-function file_validate_extensions(stdClass $file, $extensions) {
+function file_validate_extensions(File $file, $extensions) {
   $errors = array();
 
   $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i';
@@ -1748,8 +1697,8 @@ function file_validate_extensions(stdClass $file, $extensions) {
  *
  * This check is not enforced for the user #1.
  *
- * @param $file
- *   A Drupal file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $file_limit
  *   An integer specifying the maximum file size in bytes. Zero indicates that
  *   no limit should be enforced.
@@ -1763,7 +1712,7 @@ function file_validate_extensions(stdClass $file, $extensions) {
  *
  * @see hook_file_validate()
  */
-function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) {
+function file_validate_size(File $file, $file_limit = 0, $user_limit = 0) {
   global $user;
 
   $errors = array();
@@ -1785,15 +1734,15 @@ function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) {
 /**
  * Checks that the file is recognized by image_get_info() as an image.
  *
- * @param $file
- *   A Drupal file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  *
  * @return
  *   An array. If the file is not an image, it will contain an error message.
  *
  * @see hook_file_validate()
  */
-function file_validate_is_image(stdClass $file) {
+function file_validate_is_image(File $file) {
   $errors = array();
 
   $info = image_get_info($file->uri);
@@ -1810,9 +1759,8 @@ function file_validate_is_image(stdClass $file) {
  * Non-image files will be ignored. If a image toolkit is available the image
  * will be scaled to fit within the desired maximum dimensions.
  *
- * @param $file
- *   A Drupal file object. This function may resize the file affecting its
- *   size.
+ * @param Drupal\Core\File\File $file
+ *   A file entity. This function may resize the file affecting its size.
  * @param $maximum_dimensions
  *   An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If
  *   an image toolkit is installed the image will be resized down to these
@@ -1828,7 +1776,7 @@ function file_validate_is_image(stdClass $file) {
  *
  * @see hook_file_validate()
  */
-function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
+function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $minimum_dimensions = 0) {
   $errors = array();
 
   // Check first that the file is an image.
@@ -1880,8 +1828,8 @@ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0,
  *       unique.
  *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
  *
- * @return
- *   A file object, or FALSE on error.
+ * @return Drupal\Core\File\File
+ *   A file entity, or FALSE on error.
  *
  * @see file_unmanaged_save_data()
  */
@@ -1898,8 +1846,8 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM
   }
 
   if ($uri = file_unmanaged_save_data($data, $destination, $replace)) {
-    // Create a file object.
-    $file = new stdClass();
+    // Create a file entity.
+    $file = entity_create('file', array());
     $file->fid = NULL;
     $file->uri = $uri;
     $file->filename = drupal_basename($uri);
@@ -1921,7 +1869,8 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM
       $file->filename = drupal_basename($destination);
     }
 
-    return file_save($file);
+    $file->save();
+    return $file;
   }
   return FALSE;
 }
@@ -2511,15 +2460,15 @@ function file_directory_temp() {
 }
 
 /**
- * Examines a file object and returns appropriate content headers for download.
+ * Examines a file entity and returns appropriate content headers for download.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  *
  * @return
  *   An associative array of headers, as expected by file_transfer().
  */
-function file_get_content_headers($file) {
+function file_get_content_headers(File $file) {
   $name = mime_header_encode($file->filename);
   $type = mime_header_encode($file->filemime);
   // Serve images, text, and flash content for display rather than download.
diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc
index 4e3b221..43f731d 100644
--- a/core/includes/gettext.inc
+++ b/core/includes/gettext.inc
@@ -7,6 +7,8 @@
  * @todo Decouple these functions from Locale API and put to gettext_ namespace.
  */
 
+use Drupal\Core\File\File;
+
 /**
  * @defgroup locale-api-import-export Translation import/export API.
  * @{
@@ -19,8 +21,8 @@
 /**
  * Parses Gettext Portable Object information and inserts it into the database.
  *
- * @param $file
- *   Drupal file object corresponding to the PO file to import.
+ * @param Drupal\Core\File\File $file
+ *   A file entity corresponding to the PO file to import.
  * @param $langcode
  *   Language code.
  * @param $overwrite_options
@@ -32,7 +34,7 @@
  *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. All strings in the file
  *   will be saved with this customization flag.
  */
-function _locale_import_po($file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
+function _locale_import_po(File $file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
   // Try to allocate enough time to parse and import the data.
   drupal_set_time_limit(240);
 
@@ -83,8 +85,8 @@ function _locale_import_po($file, $langcode, $overwrite_options, $customized = L
  *
  * @param $op
  *   Storage operation type: db-store or mem-store.
- * @param $file
- *   Drupal file object corresponding to the PO file to import.
+ * @param Drupal\Core\File\File $file
+ *   A file entity corresponding to the PO file to import.
  * @param $overwrite_options
  *   An associative array indicating what data should be overwritten, if any.
  *   - not_customized: not customized strings should be overwritten.
@@ -95,7 +97,7 @@ function _locale_import_po($file, $langcode, $overwrite_options, $customized = L
  *   Whether the strings being imported should be saved as customized.
  *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  */
-function _locale_import_read_po($op, $file, $overwrite_options = NULL, $lang = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
+function _locale_import_read_po($op, File $file, $overwrite_options = NULL, $lang = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
 
   // The file will get closed by PHP on returning from this function.
   $fd = fopen($file->uri, 'rb');
@@ -348,12 +350,12 @@ function _locale_import_read_po($op, $file, $overwrite_options = NULL, $lang = N
  *
  * @param $message
  *   The message to be translated.
- * @param $file
- *   Drupal file object corresponding to the PO file to import.
+ * @param Drupal\Core\File\File $file
+ *   A file entity corresponding to the PO file to import.
  * @param $lineno
  *   An optional line number argument.
  */
-function _locale_import_message($message, $file, $lineno = NULL) {
+function _locale_import_message($message, File $file, $lineno = NULL) {
   $vars = array('%filename' => $file->filename);
   if (isset($lineno)) {
     $vars['%line'] = $lineno;
@@ -375,14 +377,14 @@ function _locale_import_message($message, $file, $lineno = NULL) {
  *   - customized: customized strings should be overwritten.
  * @param $lang
  *   Language to store the string in.
- * @param $file
- *   Object representation of file being imported, only required when op is
- *   'db-store'.
+ * @param Drupal\Core\File\File $file
+ *   A file entity representing the file being imported, only required when op
+ *   is 'db-store'.
  * @param $customized
  *   Whether the strings being imported should be saved as customized.
  *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  */
-function _locale_import_one_string($op, $value = NULL, $overwrite_options = NULL, $lang = NULL, $file = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
+function _locale_import_one_string($op, $value = NULL, $overwrite_options = NULL, $lang = NULL, File $file = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
   $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
   $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
   $strings = &drupal_static(__FUNCTION__ . ':strings', array());
diff --git a/core/lib/Drupal/Core/File/File.php b/core/lib/Drupal/Core/File/File.php
new file mode 100644
index 0000000..62e03a0
--- /dev/null
+++ b/core/lib/Drupal/Core/File/File.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\File\File.
+ */
+
+namespace Drupal\Core\File;
+
+use Drupal\entity\Entity;
+
+/**
+ * Defines the file entity class.
+ */
+class File extends Entity {
+
+  /**
+   * The file ID.
+   *
+   * @var integer
+   */
+  public $fid;
+
+  /**
+   * The file language code.
+   *
+   * @var string
+   */
+  public $langcode = LANGUAGE_NOT_SPECIFIED;
+
+  /**
+   * The uid of the user who is associated with the file.
+   *
+   * @var integer
+   */
+  public $uid;
+
+  /**
+   * Name of the file with no path components.
+   *
+   * This may differ from the basename of the URI if the file is renamed to
+   * avoid overwriting an existing file.
+   *
+   * @var string
+   */
+  public $filename;
+
+  /**
+   * The URI to access the file (either local or remote).
+   *
+   * @var string
+   */
+  public $uri;
+
+  /**
+   * The file's MIME type.
+   *
+   * @var string
+   */
+  public $filemime;
+
+  /**
+   * The size of the file in bytes.
+   *
+   * @var string
+   */
+  public $filesize;
+
+  /**
+   * A field indicating the status of the file.
+   *
+   * Two status are defined in core: temporary (0) and permanent (1).
+   * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed
+   * during a cron run.
+   *
+   * @var integer
+   */
+  public $status;
+
+  /**
+   * UNIX timestamp for when the file was added.
+   *
+   * @var integer
+   */
+  public $timestamp;
+
+  /**
+   * Overrides Drupal\entity\Entity::id().
+   */
+  public function id() {
+    return $this->fid;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/File/FileStorageController.php b/core/lib/Drupal/Core/File/FileStorageController.php
new file mode 100644
index 0000000..9c3d8fa
--- /dev/null
+++ b/core/lib/Drupal/Core/File/FileStorageController.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\File\FileStorageController.
+ */
+
+namespace Drupal\Core\File;
+
+use Drupal\entity\EntityDatabaseStorageController;
+use Drupal\entity\EntityInterface;
+
+/**
+ * File storage controller for files.
+ */
+class FileStorageController extends EntityDatabaseStorageController {
+
+  /**
+   * Overrides Drupal\entity\EntityDatabaseStorageController::presave().
+   */
+  protected function preSave(EntityInterface $entity) {
+    $entity->timestamp = REQUEST_TIME;
+    $entity->filesize = filesize($entity->uri);
+  }
+
+  /**
+   * Overrides Drupal\entity\EntityDatabaseStorageController::delete().
+   *
+   * file_usage_list() is called to determine if the file is being used by any
+   * modules. If the file is being used the delete will be canceled.
+   */
+  public function delete($ids) {
+    foreach (file_load_multiple($ids) as $file) {
+      // Let other modules clean up any references to the file prior to deletion.
+      module_invoke_all('file_predelete', $file);
+      module_invoke_all('entity_predelete', $file, 'file');
+
+      // Make sure the file is deleted before removing its row from the
+      // database, so UIs can still find the file in the database.
+      if (file_unmanaged_delete($file->uri)) {
+        db_delete('file_managed')->condition('fid', $file->fid)->execute();
+        db_delete('file_usage')->condition('fid', $file->fid)->execute();
+
+        // Let other modules respond to file deletion.
+        module_invoke_all('file_delete', $file);
+        module_invoke_all('entity_delete', $file, 'file');
+      }
+    }
+  }
+
+}
diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test
index dd4aa70..3bbb80e 100644
--- a/core/modules/entity/tests/entity_crud_hook_test.test
+++ b/core/modules/entity/tests/entity_crud_hook_test.test
@@ -137,7 +137,7 @@ class EntityCrudHookTestCase extends WebTestBase {
   public function testFileHooks() {
     $url = 'public://entity_crud_hook_test.file';
     file_put_contents($url, 'Test test test');
-    $file = (object) array(
+    $file = entity_create('file', array(
       'fid' => NULL,
       'uid' => 1,
       'filename' => 'entity_crud_hook_test.file',
@@ -146,9 +146,9 @@ class EntityCrudHookTestCase extends WebTestBase {
       'filesize' => filesize($url),
       'status' => 1,
       'timestamp' => REQUEST_TIME,
-    );
+    ));
     $_SESSION['entity_crud_hook_test'] = array();
-    file_save($file);
+    $file->save();
 
     $this->assertHookMessageOrder(array(
       'entity_crud_hook_test_file_presave called',
@@ -167,7 +167,7 @@ class EntityCrudHookTestCase extends WebTestBase {
 
     $_SESSION['entity_crud_hook_test'] = array();
     $file->filename = 'new.entity_crud_hook_test.file';
-    file_save($file);
+    $file->save();
 
     $this->assertHookMessageOrder(array(
       'entity_crud_hook_test_file_presave called',
@@ -177,7 +177,7 @@ class EntityCrudHookTestCase extends WebTestBase {
     ));
 
     $_SESSION['entity_crud_hook_test'] = array();
-    file_delete($file);
+    $file->delete();
 
     $this->assertHookMessageOrder(array(
       'entity_crud_hook_test_file_predelete called',
diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc
index dcb2a8c..db8e07e 100644
--- a/core/modules/file/file.field.inc
+++ b/core/modules/file/file.field.inc
@@ -926,7 +926,7 @@ function file_field_formatter_view($entity_type, $entity, $field, $instance, $la
       foreach ($items as $delta => $item) {
         $element[$delta] = array(
           '#theme' => 'file_link',
-          '#file' => (object) $item,
+          '#file' => file_load($item['fid']),
         );
       }
       break;
@@ -965,7 +965,7 @@ function theme_file_formatter_table($variables) {
   $rows = array();
   foreach ($variables['items'] as $delta => $item) {
     $rows[] = array(
-      theme('file_link', array('file' => (object) $item)),
+      theme('file_link', array('file' => file_load($item['fid']))),
       format_size($item['filesize']),
     );
   }
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 9251186..78112c6 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\entity\EntityFieldQuery;
+use Drupal\Core\File\File;
 
 // Load all Field module hooks for File.
 require_once DRUPAL_ROOT . '/core/modules/file/file.field.inc';
@@ -347,7 +348,7 @@ function file_progress_implementation() {
 /**
  * Implements hook_file_predelete().
  */
-function file_file_predelete($file) {
+function file_file_predelete(File $file) {
   // TODO: Remove references to a file that is in-use.
 }
 
@@ -601,7 +602,7 @@ function file_managed_file_submit($form, &$form_state) {
     // it's up to the implementing module to remove usages of files to have them
     // removed.
     if ($element['#file'] && $element['#file']->status == 0) {
-      file_delete($element['#file']);
+      file_delete($element['#file']->fid);
     }
     // Update both $form_state['values'] and $form_state['input'] to reflect
     // that the file has been removed, so that the form is rebuilt correctly.
@@ -631,7 +632,7 @@ function file_managed_file_submit($form, &$form_state) {
  *   The FAPI element whose values are being saved.
  *
  * @return
- *   The file object representing the file that was saved, or FALSE if no file
+ *   The file entity representing the file that was saved, or FALSE if no file
  *   was saved.
  */
 function file_managed_file_save_upload($element) {
@@ -724,7 +725,7 @@ function file_managed_file_pre_render($element) {
  *
  * @param $variables
  *   An associative array containing:
- *   - file: A file object to which the link will be created.
+ *   - file: A file entity to which the link will be created.
  *   - icon_directory: (optional) A path to a directory of icons to be used for
  *     files. Defaults to the value of the "file_icon_directory" variable.
  *
@@ -762,7 +763,7 @@ function theme_file_link($variables) {
  *
  * @param $variables
  *   An associative array containing:
- *   - file: A file object for which to make an icon.
+ *   - file: A file entity for which to make an icon.
  *   - icon_directory: (optional) A path to a directory of icons to be used for
  *     files. Defaults to the value of the "file_icon_directory" variable.
  *
@@ -778,10 +779,10 @@ function theme_file_icon($variables) {
 }
 
 /**
- * Creates a URL to the icon for a file object.
+ * Creates a URL to the icon for a file entity.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $icon_directory
  *   (optional) A path to a directory of icons to be used for files. Defaults to
  *   the value of the "file_icon_directory" variable.
@@ -789,7 +790,7 @@ function theme_file_icon($variables) {
  * @return
  *   A URL string to the icon, or FALSE if an appropriate icon cannot be found.
  */
-function file_icon_url($file, $icon_directory = NULL) {
+function file_icon_url(File $file, $icon_directory = NULL) {
   if ($icon_path = file_icon_path($file, $icon_directory)) {
     return base_path() . $icon_path;
   }
@@ -797,10 +798,10 @@ function file_icon_url($file, $icon_directory = NULL) {
 }
 
 /**
- * Creates a path to the icon for a file object.
+ * Creates a path to the icon for a file entity.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $icon_directory
  *   (optional) A path to a directory of icons to be used for files. Defaults to
  *   the value of the "file_icon_directory" variable.
@@ -809,7 +810,7 @@ function file_icon_url($file, $icon_directory = NULL) {
  *   A string to the icon as a local path, or FALSE if an appropriate icon could
  *   not be found.
  */
-function file_icon_path($file, $icon_directory = NULL) {
+function file_icon_path(File $file, $icon_directory = NULL) {
   // Use the default set of icons if none specified.
   if (!isset($icon_directory)) {
     $icon_directory = variable_get('file_icon_directory', drupal_get_path('module', 'file') . '/icons');
@@ -852,13 +853,13 @@ function file_icon_path($file, $icon_directory = NULL) {
 /**
  * Determines the generic icon MIME package based on a file's MIME type.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  *
  * @return
  *   The generic icon MIME package expected for this file.
  */
-function file_icon_map($file) {
+function file_icon_map(File $file) {
   switch ($file->filemime) {
     // Word document types.
     case 'application/msword':
@@ -984,8 +985,8 @@ function file_icon_map($file) {
 /**
  * Retrieves a list of references to a file.
  *
- * @param $file
- *   A file object.
+ * @param Drupal\Core\File\File $file
+ *   A file entity.
  * @param $field
  *   (optional) A field array to be used for this check. If given, limits the
  *   reference check to the given field.
@@ -1000,7 +1001,7 @@ function file_icon_map($file) {
  * @return
  *   An integer value.
  */
-function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
+function file_get_file_references(File $file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
   $references = drupal_static(__FUNCTION__, array());
   $fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields();
 
diff --git a/core/modules/file/tests/file.test b/core/modules/file/tests/file.test
index a826c14..ae955f0 100644
--- a/core/modules/file/tests/file.test
+++ b/core/modules/file/tests/file.test
@@ -41,7 +41,7 @@ class FileFieldTestCase extends WebTestBase {
     // Add a filesize property to files as would be read by file_load().
     $file->filesize = filesize($file->uri);
 
-    return $file;
+    return entity_create('file', (array) $file);
   }
 
   /**
@@ -809,7 +809,7 @@ class FileFieldDisplayTestCase extends FileFieldTestCase {
 
     // Check that the default formatter is displaying with the file name.
     $node = node_load($nid, NULL, TRUE);
-    $node_file = (object) $node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0];
+    $node_file = file_load($node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['fid']);
     $default_output = theme('file_link', array('file' => $node_file));
     $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.'));
 
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 3edf83c..36fbbd6 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -5,6 +5,8 @@
  * Exposes global functionality for creating image styles.
  */
 
+use Drupal\Core\File\File;
+
 /**
  * Image style constant for user presets in the database.
  */
@@ -308,7 +310,7 @@ function image_file_download($uri) {
 /**
  * Implements hook_file_move().
  */
-function image_file_move($file, $source) {
+function image_file_move(File $file, File $source) {
   // Delete any image derivatives at the original image path.
   image_path_flush($source->uri);
 }
@@ -316,7 +318,7 @@ function image_file_move($file, $source) {
 /**
  * Implements hook_file_predelete().
  */
-function image_file_predelete($file) {
+function image_file_predelete(File $file) {
   // Delete any image derivatives of this image.
   image_path_flush($file->uri);
 }
@@ -393,7 +395,7 @@ function image_field_update_field($field, $prior_field, $has_data) {
     // Is there a new file?
     if ($file_new) {
       $file_new->status = FILE_STATUS_PERMANENT;
-      file_save($file_new);
+      $file_new->save();
       file_usage_add($file_new, 'image', 'default_image', $field['id']);
     }
 
@@ -461,7 +463,7 @@ function image_field_update_instance($instance, $prior_instance) {
     // Save the new file, if present.
     if ($file_new) {
       $file_new->status = FILE_STATUS_PERMANENT;
-      file_save($file_new);
+      $file_new->save();
       file_usage_add($file_new, 'image', 'default_image', $instance['id']);
     }
     // Delete the old file, if present.
diff --git a/core/modules/image/image.test b/core/modules/image/image.test
index f9a4ec1..7a00a94 100644
--- a/core/modules/image/image.test
+++ b/core/modules/image/image.test
@@ -1290,8 +1290,8 @@ class ImageFieldDefaultImagesTestCase extends ImageFieldTestCase {
     $files = $this->drupalGetTestFiles('image');
     $default_images = array();
     foreach (array('field', 'instance', 'instance2', 'field_new', 'instance_new') as $image_target) {
-      $file = array_pop($files);
-      $file = file_save($file);
+      $file = entity_create('file', (array) array_pop($files));
+      $file->save();
       $default_images[$image_target] = $file;
     }
 
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 8cd4fd1..7a8a977 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -297,7 +297,7 @@ function locale_translate_batch_import($filepath, &$context) {
   // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
   // we can extract the language code to use for the import from the end.
   if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
-    $file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
+    $file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath));
     _locale_import_read_po('db-store', $file, array(), $langcode[2]);
     $context['results'][] = $filepath;
   }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 2fb21c7..ea0ae55 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Utility\UpdateException;
+use Drupal\Core\File\File;
 
 /**
  * @addtogroup hooks
@@ -2342,19 +2343,19 @@ function hook_stream_wrappers_alter(&$wrappers) {
 }
 
 /**
- * Load additional information into file objects.
+ * Load additional information into file entities.
  *
  * file_load_multiple() calls this hook to allow modules to load
  * additional information into each file.
  *
  * @param $files
- *   An array of file objects, indexed by fid.
+ *   An array of file entities, indexed by fid.
  *
  * @see file_load_multiple()
  * @see file_load()
  */
 function hook_file_load($files) {
-  // Add the upload specific data into the file object.
+  // Add the upload specific data into the file entity.
   $result = db_query('SELECT * FROM {upload} u WHERE u.fid IN (:fids)', array(':fids' => array_keys($files)))->fetchAll(PDO::FETCH_ASSOC);
   foreach ($result as $record) {
     foreach ($record as $key => $value) {
@@ -2369,15 +2370,15 @@ function hook_file_load($files) {
  * This hook lets modules perform additional validation on files. They're able
  * to report a failure by returning one or more error messages.
  *
- * @param $file
- *   The file object being validated.
+ * @param Drupal\Core\File\File $file
+ *   The file entity being validated.
  * @return
  *   An array of error messages. If there are no problems with the file return
  *   an empty array.
  *
  * @see file_validate()
  */
-function hook_file_validate($file) {
+function hook_file_validate(Drupal\Core\File\File $file) {
   $errors = array();
 
   if (empty($file->filename)) {
@@ -2397,12 +2398,10 @@ function hook_file_validate($file) {
  * doesn't distinguish between files created as a result of a copy or those
  * created by an upload.
  *
- * @param $file
- *   The file that has just been created.
- *
- * @see file_save()
+ * @param Drupal\Core\File\File $file
+ *   The file entity that has just been created.
  */
-function hook_file_presave($file) {
+function hook_file_presave(Drupal\Core\File\File $file) {
   // Change the file timestamp to an hour prior.
   $file->timestamp -= 3600;
 }
@@ -2414,12 +2413,10 @@ function hook_file_presave($file) {
  * doesn't distinguish between files created as a result of a copy or those
  * created by an upload.
  *
- * @param $file
+ * @param Drupal\Core\File\File $file
  *   The file that has been added.
- *
- * @see file_save()
  */
-function hook_file_insert($file) {
+function hook_file_insert(Drupal\Core\File\File $file) {
   // Add a message to the log, if the file is a jpg
   $validate = file_validate_extensions($file, 'jpg');
   if (empty($validate)) {
@@ -2430,59 +2427,57 @@ function hook_file_insert($file) {
 /**
  * Respond to a file being updated.
  *
- * This hook is called when file_save() is called on an existing file.
+ * This hook is called when an existing file is saved.
  *
- * @param $file
+ * @param Drupal\Core\File\File $file
  *   The file that has just been updated.
- *
- * @see file_save()
  */
-function hook_file_update($file) {
+function hook_file_update(Drupal\Core\File\File $file) {
 
 }
 
 /**
  * Respond to a file that has been copied.
  *
- * @param $file
- *   The newly copied file object.
- * @param $source
+ * @param Drupal\Core\File\File $file
+ *   The newly copied file entity.
+ * @param Drupal\Core\File\File $source
  *   The original file before the copy.
  *
  * @see file_copy()
  */
-function hook_file_copy($file, $source) {
+function hook_file_copy(Drupal\Core\File\File $file, Drupal\Core\File\File $source) {
 
 }
 
 /**
  * Respond to a file that has been moved.
  *
- * @param $file
- *   The updated file object after the move.
- * @param $source
- *   The original file object before the move.
+ * @param Drupal\Core\File\File $file
+ *   The updated file entity after the move.
+ * @param Drupal\Core\File\File $source
+ *   The original file entity before the move.
  *
  * @see file_move()
  */
-function hook_file_move($file, $source) {
+function hook_file_move(Drupal\Core\File\File $file, Drupal\Core\File\File $source) {
 
 }
 
 /**
  * Act prior to file deletion.
  *
- * This hook is invoked from file_delete() before the file is removed from the
+ * This hook is invoked when deleting a file before the file is removed from the
  * filesystem and before its records are removed from the database.
  *
- * @param $file
+ * @param Drupal\Core\File\File $file
  *   The file that is about to be deleted.
  *
  * @see hook_file_delete()
- * @see file_delete()
+ * @see Drupal\Core\File\FileStorageController::delete()
  * @see upload_file_delete()
  */
-function hook_file_predelete($file) {
+function hook_file_predelete(Drupal\Core\File\File $file) {
   // Delete all information associated with the file.
   db_delete('upload')->condition('fid', $file->fid)->execute();
 }
@@ -2490,16 +2485,16 @@ function hook_file_predelete($file) {
 /**
  * Respond to file deletion.
  *
- * This hook is invoked from file_delete() after the file has been removed from
+ * This hook is invoked after the file has been removed from
  * the filesystem and after its records have been removed from the database.
  *
- * @param $file
+ * @param Drupal\Core\File\File $file
  *   The file that has just been deleted.
  *
  * @see hook_file_predelete()
- * @see file_delete()
+ * @see Drupal\Core\File\FileStorageController::delete()
  */
-function hook_file_delete($file) {
+function hook_file_delete(Drupal\Core\File\File $file) {
   // Delete all information associated with the file.
   db_delete('upload')->condition('fid', $file->fid)->execute();
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ebc5dcf..4294f80 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -269,6 +269,8 @@ function system_entity_info() {
     'file' => array(
       'label' => t('File'),
       'base table' => 'file_managed',
+      'controller class' => 'Drupal\Core\File\FileStorageController',
+      'entity class' => 'Drupal\Core\File\File',
       'entity keys' => array(
         'id' => 'fid',
         'label' => 'filename',
@@ -3076,7 +3078,7 @@ function system_cron() {
       $references = file_usage_list($file);
       if (empty($references)) {
         if (file_exists($file->uri)) {
-          file_delete($file);
+          $file->delete();
         }
         else {
           watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->uri), WATCHDOG_ERROR);
diff --git a/core/modules/system/system.test b/core/modules/system/system.test
index 40f590e..4cd65a5 100644
--- a/core/modules/system/system.test
+++ b/core/modules/system/system.test
@@ -867,8 +867,7 @@ class CronRunTestCase extends WebTestBase {
    * Ensure that temporary files are removed.
    *
    * Create files for all the possible combinations of age and status. We are
-   * using UPDATE statements rather than file_save() because it would set the
-   * timestamp.
+   * using UPDATE statements because using the API would set the timestamp.
    */
   function testTempFileCleanup() {
     // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
diff --git a/core/modules/system/tests/file.test b/core/modules/system/tests/file.test
index f679f98..e925695 100644
--- a/core/modules/system/tests/file.test
+++ b/core/modules/system/tests/file.test
@@ -7,11 +7,12 @@
  */
 
 use Drupal\simpletest\WebTestBase;
+use Drupal\Core\File\File;
 
 /**
  * Helper validator that returns the $errors parameter.
  */
-function file_test_validator($file, $errors) {
+function file_test_validator(File $file, $errors) {
   return $errors;
 }
 
@@ -238,11 +239,11 @@ class FileTestCase extends WebTestBase {
     $file->timestamp = REQUEST_TIME;
     $file->filesize = filesize($file->uri);
     $file->status = 0;
-    // Write the record directly rather than calling file_save() so we don't
-    // invoke the hooks.
+    // Write the record directly rather than using the API so we don't invoke
+    // the hooks.
     $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
 
-    return $file;
+    return entity_create('file', (array) $file);
   }
 }
 
@@ -388,11 +389,11 @@ class FileValidatorTest extends WebTestBase {
   function setUp() {
     parent::setUp();
 
-    $this->image = new stdClass();
+    $this->image = entity_create('file', array());
     $this->image->uri = 'core/misc/druplicon.png';
     $this->image->filename = drupal_basename($this->image->uri);
 
-    $this->non_image = new stdClass();
+    $this->non_image = entity_create('file', array());
     $this->non_image->uri = 'core/misc/jquery.js';
     $this->non_image->filename = drupal_basename($this->non_image->uri);
   }
@@ -401,8 +402,7 @@ class FileValidatorTest extends WebTestBase {
    * Test the file_validate_extensions() function.
    */
   function testFileValidateExtensions() {
-    $file = new stdClass();
-    $file->filename = 'asdf.txt';
+    $file = entity_create('file', array('filename' => 'asdf.txt'));
     $errors = file_validate_extensions($file, 'asdf txt pork');
     $this->assertEqual(count($errors), 0, t('Valid extension accepted.'), 'File');
 
@@ -471,8 +471,8 @@ class FileValidatorTest extends WebTestBase {
    *  This will ensure the filename length is valid.
    */
   function testFileValidateNameLength() {
-    // Create a new file object.
-    $file = new stdClass();
+    // Create a new file entity.
+    $file = entity_create('file', array());
 
     // Add a filename with an allowed length and test it.
     $file->filename = str_repeat('x', 240);
@@ -503,8 +503,7 @@ class FileValidatorTest extends WebTestBase {
     // Run these test as uid = 1.
     $user = user_load(1);
 
-    $file = new stdClass();
-    $file->filesize = 999999;
+    $file = entity_create('file', array('filesize' => 999999));
     $errors = file_validate_size($file, 1, 1);
     $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File');
 
@@ -512,8 +511,7 @@ class FileValidatorTest extends WebTestBase {
     $user = $this->drupalCreateUser();
 
     // Create a file with a size of 1000 bytes, and quotas of only 1 byte.
-    $file = new stdClass();
-    $file->filesize = 1000;
+    $file = entity_create('file', array('filesize' => 1000));
     $errors = file_validate_size($file, 0, 0);
     $this->assertEqual(count($errors), 0, t('No limits means no errors.'), 'File');
     $errors = file_validate_size($file, 1, 0);
@@ -612,7 +610,7 @@ class FileSaveUploadTest extends FileHookTestCase {
     $this->drupalLogin($account);
 
     $image_files = $this->drupalGetTestFiles('image');
-    $this->image = current($image_files);
+    $this->image = entity_create('file', (array) current($image_files));
 
     list(, $this->image_extension) = explode('.', $this->image->filename);
     $this->assertTrue(is_file($this->image->uri), t("The image file we're going to upload exists."));
@@ -1564,8 +1562,8 @@ class FileDeleteTest extends FileHookTestCase {
 
     // Check that deletion removes the file and database record.
     $this->assertTrue(is_file($file->uri), t('File exists.'));
-    file_delete($file);
-    $this->assertFileHooksCalled(array('delete'));
+    $file->delete();
+    $this->assertFileHooksCalled(array('delete', 'load'));
     $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.'));
     $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
   }
@@ -1808,7 +1806,7 @@ class FileCopyTest extends FileHookTestCase {
     $this->assertFileHooksCalled(array('copy', 'insert'));
 
     $this->assertDifferentFile($source, $result);
-    $this->assertEqual($result->uri, $desired_uri, t('The copied file object has the desired filepath.'));
+    $this->assertEqual($result->uri, $desired_uri, t('The copied file entity has the desired filepath.'));
     $this->assertTrue(file_exists($source->uri), t('The original file still exists.'));
     $this->assertTrue(file_exists($result->uri), t('The copied file exists.'));
 
@@ -1964,7 +1962,7 @@ class FileLoadTest extends FileHookTestCase {
    * Load a single file and ensure that the correct values are returned.
    */
   function testSingleValues() {
-    // Create a new file object from scratch so we know the values.
+    // Create a new file entity from scratch so we know the values.
     $file = $this->createFile('druplicon.txt', NULL, 'public');
 
     $by_fid_file = file_load($file->fid);
@@ -1982,7 +1980,7 @@ class FileLoadTest extends FileHookTestCase {
    * This will test loading file data from the database.
    */
   function testMultiple() {
-    // Create a new file object.
+    // Create a new file entity.
     $file = $this->createFile('druplicon.txt', NULL, 'public');
 
     // Load by path.
@@ -2006,73 +2004,72 @@ class FileLoadTest extends FileHookTestCase {
 }
 
 /**
- * Tests the file_save() function.
+ * Tests saving files.
  */
 class FileSaveTest extends FileHookTestCase {
   public static function getInfo() {
     return array(
       'name' => 'File saving',
-      'description' => 'Tests the file_save() function.',
+      'description' => 'File saving tests',
       'group' => 'File API',
     );
   }
 
   function testFileSave() {
-    // Create a new file object.
-    $file = array(
+    // Create a new file entity.
+    $file = entity_create('file', array(
       'uid' => 1,
       'filename' => 'druplicon.txt',
       'uri' => 'public://druplicon.txt',
       'filemime' => 'text/plain',
       'timestamp' => 1,
       'status' => FILE_STATUS_PERMANENT,
-    );
-    $file = (object) $file;
+    ));
     file_put_contents($file->uri, 'hello world');
 
     // Save it, inserting a new record.
-    $saved_file = file_save($file);
+    $file->save();
 
     // Check that the correct hooks were called.
     $this->assertFileHooksCalled(array('insert'));
 
-    $this->assertNotNull($saved_file, t("Saving the file should give us back a file object."), 'File');
-    $this->assertTrue($saved_file->fid > 0, t("A new file ID is set when saving a new file to the database."), 'File');
-    $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
+    $this->assertNotNull($file, t("Saving the file should give us back a file entity."), 'File');
+    $this->assertTrue($file->fid > 0, t("A new file ID is set when saving a new file to the database."), 'File');
+    $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_OBJ);
     $this->assertNotNull($loaded_file, t("Record exists in the database."));
     $this->assertEqual($loaded_file->status, $file->status, t("Status was saved correctly."));
-    $this->assertEqual($saved_file->filesize, filesize($file->uri), t("File size was set correctly."), 'File');
-    $this->assertTrue($saved_file->timestamp > 1, t("File size was set correctly."), 'File');
+    $this->assertEqual($file->filesize, filesize($file->uri), t("File size was set correctly."), 'File');
+    $this->assertTrue($file->timestamp > 1, t("File size was set correctly."), 'File');
     $this->assertEqual($loaded_file->langcode, LANGUAGE_NOT_SPECIFIED, t("Langcode was defaulted correctly."));
 
     // Resave the file, updating the existing record.
     file_test_reset();
-    $saved_file->status = 7;
-    $saved_file->langcode = 'en';
-    $resaved_file = file_save($saved_file);
+    $file->status = 7;
+    $file->langcode = 'en';
+    $file->save();
 
     // Check that the correct hooks were called.
     $this->assertFileHooksCalled(array('load', 'update'));
 
-    $this->assertEqual($resaved_file->fid, $saved_file->fid, t("The file ID of an existing file is not changed when updating the database."), 'File');
-    $this->assertTrue($resaved_file->timestamp >= $saved_file->timestamp, t("Timestamp didn't go backwards."), 'File');
-    $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
+    $this->assertEqual($file->fid, $file->fid, t("The file ID of an existing file is not changed when updating the database."), 'File');
+    $this->assertTrue($file->timestamp >= $file->timestamp, t("Timestamp didn't go backwards."), 'File');
+    $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_OBJ);
     $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File');
-    $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly."));
+    $this->assertEqual($loaded_file->status, $file->status, t("Status was saved correctly."));
     $this->assertEqual($loaded_file->langcode, 'en', t("Langcode was saved correctly."));
 
     // Try to insert a second file with the same name apart from case insensitivity
     // to ensure the 'uri' index allows for filenames with different cases.
-    $file = (object) array(
+    $file = entity_create('file', array(
       'uid' => 1,
       'filename' => 'DRUPLICON.txt',
       'uri' => 'public://DRUPLICON.txt',
       'filemime' => 'text/plain',
       'timestamp' => 1,
       'status' => FILE_STATUS_PERMANENT,
-    );
+    ));
     file_put_contents($file->uri, 'hello world');
-    file_save($file);
+    $file->save();
   }
 }
 
@@ -2503,7 +2500,7 @@ class FileDownloadTest extends FileTestCase {
       $this->assertRaw(file_get_contents($file->uri), t('Contents of the file are correct.'));
     }
 
-    file_delete($file);
+    $file->delete();
   }
 }
 
diff --git a/core/modules/system/tests/image.test b/core/modules/system/tests/image.test
index ab9eaa4..8639dd6 100644
--- a/core/modules/system/tests/image.test
+++ b/core/modules/system/tests/image.test
@@ -486,7 +486,7 @@ class ImageFileMoveTest extends ImageToolkitTestCase {
    */
   function testNormal() {
     // Pick a file for testing.
-    $file = current($this->drupalGetTestFiles('image'));
+    $file = entity_create('file', (array) current($this->drupalGetTestFiles('image')));
 
     // Create derivative image.
     $style = image_style_load(key(image_styles()));
diff --git a/core/modules/system/tests/modules/file_test/file_test.module b/core/modules/system/tests/modules/file_test/file_test.module
index 8a9a84a..a31c1d0 100644
--- a/core/modules/system/tests/modules/file_test/file_test.module
+++ b/core/modules/system/tests/modules/file_test/file_test.module
@@ -10,6 +10,7 @@
 
 use Drupal\Core\StreamWrapper\LocalStream;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\File\File;
 
 const FILE_URL_TEST_CDN_1 = 'http://cdn1.example.com';
 const FILE_URL_TEST_CDN_2 = 'http://cdn2.example.com';
@@ -253,7 +254,7 @@ function file_test_set_return($op, $value) {
  */
 function file_test_file_load($files) {
   foreach ($files as $file) {
-    _file_test_log_call('load', array($file));
+    _file_test_log_call('load', array($file->fid));
     // Assign a value on the object so that we can test that the $file is passed
     // by reference.
     $file->file_test['loaded'] = TRUE;
@@ -263,8 +264,8 @@ function file_test_file_load($files) {
 /**
  * Implements hook_file_validate().
  */
-function file_test_file_validate($file) {
-  _file_test_log_call('validate', array($file));
+function file_test_file_validate(File $file) {
+  _file_test_log_call('validate', array($file->fid));
   return _file_test_get_return('validate');
 }
 
@@ -279,36 +280,36 @@ function file_test_file_download($uri) {
 /**
  * Implements hook_file_insert().
  */
-function file_test_file_insert($file) {
-  _file_test_log_call('insert', array($file));
+function file_test_file_insert(File $file) {
+  _file_test_log_call('insert', array($file->fid));
 }
 
 /**
  * Implements hook_file_update().
  */
-function file_test_file_update($file) {
-  _file_test_log_call('update', array($file));
+function file_test_file_update(File $file) {
+  _file_test_log_call('update', array($file->fid));
 }
 
 /**
  * Implements hook_file_copy().
  */
-function file_test_file_copy($file, $source) {
-  _file_test_log_call('copy', array($file, $source));
+function file_test_file_copy(File $file, $source) {
+  _file_test_log_call('copy', array($file->fid, $source->fid));
 }
 
 /**
  * Implements hook_file_move().
  */
-function file_test_file_move($file, $source) {
-  _file_test_log_call('move', array($file, $source));
+function file_test_file_move(File $file, File $source) {
+  _file_test_log_call('move', array($file->fid, $source->fid));
 }
 
 /**
  * Implements hook_file_predelete().
  */
-function file_test_file_predelete($file) {
-  _file_test_log_call('delete', array($file));
+function file_test_file_predelete(File $file) {
+  _file_test_log_call('delete', array($file->fid));
 }
 
 /**
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index 347e7e1..78811c9 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -106,7 +106,7 @@ class UserStorageController extends EntityDatabaseStorageController {
     elseif (!empty($entity->picture_delete)) {
       $entity->picture = 0;
       file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid);
-      file_delete($entity->original->picture);
+      file_delete($entity->original->picture->fid);
     }
 
     if (!$entity->isNew()) {
@@ -125,15 +125,14 @@ class UserStorageController extends EntityDatabaseStorageController {
 
           // Move the temporary file into the final location.
           if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) {
-            $picture->status = FILE_STATUS_PERMANENT;
-            $entity->picture = file_save($picture);
+            $entity->picture = $picture;
             file_usage_add($picture, 'user', 'user', $entity->uid);
           }
         }
         // Delete the previous picture if it was deleted or replaced.
         if (!empty($entity->original->picture->fid)) {
           file_usage_delete($entity->original->picture, 'user', 'user', $entity->uid);
-          file_delete($entity->original->picture);
+          file_delete($entity->original->picture->fid);
         }
       }
       $entity->picture = empty($entity->picture->fid) ? 0 : $entity->picture->fid;
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index cfde270..80a70b2 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\File\File;
 
 /**
  * @file
@@ -592,7 +593,7 @@ function user_file_download($uri) {
 /**
  * Implements hook_file_move().
  */
-function user_file_move($file, $source) {
+function user_file_move(File $file, File $source) {
   // If a user's picture is replaced with a new one, update the record in
   // the users table.
   if (isset($file->fid) && isset($source->fid) && $file->fid != $source->fid) {
@@ -608,7 +609,7 @@ function user_file_move($file, $source) {
 /**
  * Implements hook_file_predelete().
  */
-function user_file_predelete($file) {
+function user_file_predelete(File $file) {
   // Remove any references to the file.
   db_update('users')
     ->fields(array('picture' => 0))
@@ -1228,7 +1229,7 @@ function template_preprocess_user_picture(&$variables) {
   if (variable_get('user_pictures', 0)) {
     $account = $variables['account'];
     if (!empty($account->picture)) {
-      // @TODO: Ideally this function would only be passed file objects, but
+      // @TODO: Ideally this function would only be passed file entities, but
       // since there's a lot of legacy code that JOINs the {users} table to
       // {node} or {comments} and passes the results into this function if we
       // a numeric value in the picture field we'll assume it's a file id
-- 
1.7.4.msysgit.0

