9

Found a handful of questions on here about this with no answer, so hopefully, someone can point me in the right direction...

I'm trying to create and save a csv file to storage, then update the DB in Laravel. I can create the file successfully, and I can update the DB successfully... but I'm stuck on putting them both together. In my controller, I have this for creating the file (taken from here):

 public function updatePaymentConfirm(Request $request) {

   $users = User::all();
   $fileName = 'test.csv';

   $headers = array(
      "Content-type"        => "text/csv",
      "Content-Disposition" => "attachment; filename=$fileName",
      "Pragma"              => "no-cache",
      "Cache-Control"       => "must-revalidate, post-check=0, pre-check=0",
      "Expires"             => "0"
   );

   $columns = array('First Name', 'Email');

   $callback = function() use($users, $columns) {
      $file = fopen('php://output', 'w');
      fputcsv($file, $columns);

      foreach ($users as $user) {
          $row['First Name']  = $user->first_name;
          $row['Email']    = $user->email;

          fputcsv($file, array($row['First Name'], $row['Email']));
      }

         fclose($file);
   };

   // return response()->stream($callback, 200, $headers);
 }

When the function completes, the last line (that's commented out) prompts the user to download the newly created file (which is not the functionality I'm looking for). I tried adding this to my controller in its place for saving to storage and also updating the database:

   $fileModel = new UserDocument;

   if($callback) {
       $filePath = $callback->storeAs('uploads', $fileName, 'public');

       $fileModel->name = $fileName;
       $fileModel->file_path = '/storage/' . $filePath;
       $fileModel->save();

       return back()
       ->with('success','File has been uploaded.')
       ->with('file', $fileName);
    }

It saves a row to the db, albeit incorrectly, but it doesn't save the file to storage. I've reworked the $filePath line a million times, but I keep getting this error Call to a member function storeAs() on resource or something similar. I'm relatively new to working with Laravel, so I'm not sure what I should be looking for. Thoughts?

8
  • You're getting that error because you've defined $callback as a function that doesn't return anything, and then you're trying to call the storeAs method on it. Saving uploaded files and returning files for download are two entirely opposite actions and would each have different controller methods with little or no code shared between them.
    – miken32
    Commented Nov 23, 2020 at 1:41
  • @miken32 - the second part is a piece I grabbed from a controller for uploading a file, so that makes sense. I'm not trying to return the file for download (or return the file at all to the user) - I'm trying to take the CSV that's created and store/save/put it in storage. It doesn't even need the if statement, but I'm honestly trying anything at this point. Commented Nov 23, 2020 at 2:31
  • 1
    Ok this is way too broad to answer effectively, but in short: 1 make a temp file; 2 write the CSV stuff to it; 3 save the temp file to storage; 4 write your info to the database.
    – miken32
    Commented Nov 23, 2020 at 2:54
  • 1
    Any help is better than no help! Thank you! :) I'll see what I can come up with from this. Commented Nov 23, 2020 at 2:57
  • 1
    php://output is a virtual "file" that just goes to standard output. On command line that's the screen, and on a web server it's the client browser. Glad you got it sorted out.
    – miken32
    Commented Nov 23, 2020 at 5:53

3 Answers 3

11

Removed everything and started over... got it! And for anyone else running into the same issue: just calling for a file that doesn't exist creates the file (unless the file exists - then it updates it), so you don't have to create a temp file or use $file = fopen('php://output', 'w'); to create the file. It'll automatically "save" the newly generated file in the file path you specified when you fclose() out of the file.

The only thing I'll note is that the file path has to exist (the file doesn't, but the file path does). In my instance, the file path already exists, but if yours doesn't or if you're not sure if it does, check to see if it exists, and then make the directory.

public function updatePaymentConfirm(Request $request) {

    $user = Auth::user();

    $path = storage_path('app/public/docs/user_docs/'.$user->id);

    $fileName = $user->ein.'.csv';

    $file = fopen($path.$fileName, 'w');

    $columns = array('First Name', 'Email Address');

    fputcsv($file, $columns);

        $data = [
            'First Name' => $user->first_name,  
            'Email Address' => $user->email,    
        ];


    fputcsv($file, $data);

    fclose($file);

    $symlink = 'public/docs/user_docs/'.$user->id.'/';

    $fileModel = new UserDocument;
    $fileModel->name = 'csv';
    $fileModel->file_path = $symlink.$fileName;
    $fileModel->save();

    return redirect()->route('completed');

}

** UPDATE **

Everything worked perfectly locally, and when I pushed this to production, I received this error 🙄:

fopen(https://..../12-3456789.csv): failed to open stream: HTTP wrapper does not support writeable connections.

I'm saving to an s3 bucket, and I had to rework the entire process. You can't create and/or write to a file in the directory. I had to create a temp file first. Here's where I landed:

    $user = Auth::user();
    
    $s3 = Storage::disk('s3');
    $storage = Storage::disk('s3')->url('/');
    $path = 'public/docs/user_docs/'.$user->id.'/';

    $csvFile = tmpfile();
    $csvPath = stream_get_meta_data($csvFile)['uri'];

    $fd = fopen($csvPath, 'w');

    $columns = array('First Name', 'Email Address');
    $data = array(
            'First Name' => $user->first_name,  
            'Email Address' => $user->email,    
          );

    fputcsv($fd, $columns);
    fputcsv($fd, $data);

    fclose($fd);

    $s3->putFileAs('', $csvPath, $path.$user->ein.'.csv');
2

Today I have fixed it with this snipe:

// output up to 5MB is kept in memory, if it becomes bigger it will
// automatically be written to a temporary file
$csv = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');

fputcsv($csv, array('blah','blah'));
rewind($csv);
$output = stream_get_contents($csv);

// Put the content directly in file into the disk
Storage::disk('myDisk')->put("report.csv", $output);
2

This code is easy and functional, use Laravel Storage Class https://laravel.com/docs/9.x/filesystem#main-content

use Illuminate\Support\Facades\Storage;

// data array
$results = [
  ['id' => 0, 'name' => 'David', 'parent' => 1],
  ['id' => 1, 'name' => 'Ron', 'parent' => 0],
  ['id' => 2, 'name' => 'Mark', 'parent' => 1]
];

// create a variable to store data
$pages = "id,name,parent\n"; // use " not ' or \n not working

// use foreach to data
foreach ($results as $where) {
  $pages .= "{$where['id']},{$where['name']},{$where['parent']}\n";
}

// use Fecades Laravel Storage
Storage::disk('local')->put('file.csv', $pages);
1
  • That method won't work if your data has commas in it. The reason to use fputcsv would be that it handles the edge cases of the csv format.
    – Jay K
    Commented Feb 14 at 21:35

Not the answer you're looking for? Browse other questions tagged or ask your own question.