Laravel: Export customer record as CSV











up vote
0
down vote

favorite












Using Laravel, I need to export some value from the DB in a CSV and then upload the CSV via sFTP or return it in a Response.



I'm trying to be use S.O.L.I.D. principles but with this scenario, I'm not sure how to proceed. As I understand I should have one class that handles the CSV, one that handles the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case). But I don't understand how I can separate them.



Later one, I will have more Model data to export.



<?php

namespace AppServices;

use AppLine;
use SymfonyComponentHttpFoundationStreamedResponse;
use Storage;

class LinesCsv
{
const DOCUMENT_TYPE = 20;
const DELIMITER = ';';

public function exportCSVFileToSftp($filename = 'export.csv')
{
$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);
return Storage::disk('sftp')->put($filename, $handle);
}

public function exportCSVFileToResponse($filename = 'export.csv')
{
return new StreamedResponse(function () use ($filename) {
$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}


public function buildCsv($handle, $header = false)
{
if ($header) {
fputcsv(
$handle,
array_keys($this->lineMapping(Line::first())),
self::DELIMITER
);
}

Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
})
->chunk(200, function ($lines) use ($handle) {
foreach ($lines as $line) {
fputcsv(
$handle,
$this->lineMapping($line),
self::DELIMITER
);
}
});

return $handle;
}

protected function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}









share|improve this question









New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
    – Edson Horacio Junior
    yesterday










  • I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
    – cbaconnier
    yesterday















up vote
0
down vote

favorite












Using Laravel, I need to export some value from the DB in a CSV and then upload the CSV via sFTP or return it in a Response.



I'm trying to be use S.O.L.I.D. principles but with this scenario, I'm not sure how to proceed. As I understand I should have one class that handles the CSV, one that handles the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case). But I don't understand how I can separate them.



Later one, I will have more Model data to export.



<?php

namespace AppServices;

use AppLine;
use SymfonyComponentHttpFoundationStreamedResponse;
use Storage;

class LinesCsv
{
const DOCUMENT_TYPE = 20;
const DELIMITER = ';';

public function exportCSVFileToSftp($filename = 'export.csv')
{
$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);
return Storage::disk('sftp')->put($filename, $handle);
}

public function exportCSVFileToResponse($filename = 'export.csv')
{
return new StreamedResponse(function () use ($filename) {
$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}


public function buildCsv($handle, $header = false)
{
if ($header) {
fputcsv(
$handle,
array_keys($this->lineMapping(Line::first())),
self::DELIMITER
);
}

Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
})
->chunk(200, function ($lines) use ($handle) {
foreach ($lines as $line) {
fputcsv(
$handle,
$this->lineMapping($line),
self::DELIMITER
);
}
});

return $handle;
}

protected function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}









share|improve this question









New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
    – Edson Horacio Junior
    yesterday










  • I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
    – cbaconnier
    yesterday













up vote
0
down vote

favorite









up vote
0
down vote

favorite











Using Laravel, I need to export some value from the DB in a CSV and then upload the CSV via sFTP or return it in a Response.



I'm trying to be use S.O.L.I.D. principles but with this scenario, I'm not sure how to proceed. As I understand I should have one class that handles the CSV, one that handles the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case). But I don't understand how I can separate them.



Later one, I will have more Model data to export.



<?php

namespace AppServices;

use AppLine;
use SymfonyComponentHttpFoundationStreamedResponse;
use Storage;

class LinesCsv
{
const DOCUMENT_TYPE = 20;
const DELIMITER = ';';

public function exportCSVFileToSftp($filename = 'export.csv')
{
$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);
return Storage::disk('sftp')->put($filename, $handle);
}

public function exportCSVFileToResponse($filename = 'export.csv')
{
return new StreamedResponse(function () use ($filename) {
$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}


public function buildCsv($handle, $header = false)
{
if ($header) {
fputcsv(
$handle,
array_keys($this->lineMapping(Line::first())),
self::DELIMITER
);
}

Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
})
->chunk(200, function ($lines) use ($handle) {
foreach ($lines as $line) {
fputcsv(
$handle,
$this->lineMapping($line),
self::DELIMITER
);
}
});

return $handle;
}

protected function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}









share|improve this question









New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











Using Laravel, I need to export some value from the DB in a CSV and then upload the CSV via sFTP or return it in a Response.



I'm trying to be use S.O.L.I.D. principles but with this scenario, I'm not sure how to proceed. As I understand I should have one class that handles the CSV, one that handles the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case). But I don't understand how I can separate them.



Later one, I will have more Model data to export.



<?php

namespace AppServices;

use AppLine;
use SymfonyComponentHttpFoundationStreamedResponse;
use Storage;

class LinesCsv
{
const DOCUMENT_TYPE = 20;
const DELIMITER = ';';

public function exportCSVFileToSftp($filename = 'export.csv')
{
$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);
return Storage::disk('sftp')->put($filename, $handle);
}

public function exportCSVFileToResponse($filename = 'export.csv')
{
return new StreamedResponse(function () use ($filename) {
$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}


public function buildCsv($handle, $header = false)
{
if ($header) {
fputcsv(
$handle,
array_keys($this->lineMapping(Line::first())),
self::DELIMITER
);
}

Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
})
->chunk(200, function ($lines) use ($handle) {
foreach ($lines as $line) {
fputcsv(
$handle,
$this->lineMapping($line),
self::DELIMITER
);
}
});

return $handle;
}

protected function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}






php object-oriented csv laravel network-file-transfer






share|improve this question









New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question









New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question








edited yesterday









Sᴀᴍ Onᴇᴌᴀ

7,71061748




7,71061748






New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked Nov 15 at 7:54









cbaconnier

566




566




New contributor




cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






cbaconnier is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.












  • What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
    – Edson Horacio Junior
    yesterday










  • I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
    – cbaconnier
    yesterday


















  • What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
    – Edson Horacio Junior
    yesterday










  • I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
    – cbaconnier
    yesterday
















What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
– Edson Horacio Junior
yesterday




What model are you using here, is it Line? I see it query the DB but the columns names doesn't seem to have anything to do with Line
– Edson Horacio Junior
yesterday












I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
– cbaconnier
yesterday




I reduced the content of lineMapping() for the question since it's not relevant. the Line model is a line in an invoice. with(['invoice', 'invoice.customer', 'item']) is an eager load to load the Invoice, the Customer and the Item. In the CSV, every row will have information about the the line, the item, the invoice and the customer. The CSV is templated to be imported in an external accounting software.
– cbaconnier
yesterday










2 Answers
2






active

oldest

votes

















up vote
2
down vote



accepted
+50










Try to think about this in a more generic way, you basically have:




  1. Select data from DB

  2. Put that data in a CSV (will you in the future want to use other formats like XLSX, etc? If so, we should abstract this step too, but I wont do this right now)

  3. Return that CSV file (doesn't matter to who you're returning)


So we can create a few classes from those steps




  1. Any class that can query the DB, your app probably has a bunch of them

  2. AbstractWriter - this class is an abstraction/interface to write anywhere, you'll need to implement it for specific formats, like Writer/Csv, Writer/Xlsx, Writer/JSON, etc.

  3. AbstractFileReturner - this class is an abstraction/interface to return it to anyone, you'll need to implement it for specific cases, AbstractFileReturner/like Sftp, AbstractFileReturner/HttpResponse, etc.


Any time you need more File formats or Returner, you just implement the AbstractWriter or FileReturner respectively one more time.



Doing all these, you'll be using Single Responsability Principle (each class do only one thing) and Open/Closed (the abstractions/interfaces are closed for modification, but open for extension).





Real code



Ok so I came up with a little bit of code having SOLID in mind and to make it easy to add more file formats and and way outs (Sftp, Response, etc).



This is a photo of the layers I tried to create, it might make the code more understandable.
enter image description here



Now embrace yourself for there are coming a few classes.



I didn't test this code and doens't know much of Laravel, so there might be a few bugs, but you'll get the big picture.



First, a sample controller and model which I thought was your Line.



class SomeModel
{
// the chunk does not go here, it will go inside who's putting the data into the csv
public static function someQuery()
{
return Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
});
}

public static function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}

class SomeController
{
public function exportCsvSftpAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output.csv';

$exporter = new SftpCsvExporter($dataCollection, $filename);
$exporter->export();
}

public function exportCsvResponseAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output2.csv';

$exporter = new ResponseCsvExporter($dataCollection, $filename);
$exporter->export();
}
}


Than there's the first layer: Export, I created an interface to say what it should do (don't mind about how now) and a abstract class that implements some parts of it.



interface Exportable
{
protected function createFile();

protected function write($line);

protected function deliver($filename);
}

abstract class Export implements Exportable
{
protected $fileHandle = null;
private $dataCollection;
private $filename;

public function __construct($dataCollection, $filename)
{
$this->createFile();

// @TODO You should add validation for $dataCollection like
// checking if it has data, if it really is a Collection so we can call chunk, etc
// And check $filename for invalid characters
$this->dataCollection = $dataCollection;
$this->filename = $filename;
}

protected abstract function createFile();

protected abstract function write($line);

protected abstract function deliver($filename);

// this method is who makes it all work together
// and let the controller be so simple
public function export()
{
$this->dataCollection->chunk(200, function ($lines) {
foreach ($lines as $line) {
$this->write($line);
}
});

$this->deliver($this->filename);
}
}


Now there's the second layer: Csv, in this layer you will be able to add more file formats in the future if needed.

This layer knows how to write, but doesn't know how to open nor deliver the file.



abstract class CsvExporter extends Export
{
const DELIMITER = ';';

private $headerWritten = false;

protected function write($line)
{
if (!$this->headerWritten) {
fputcsv(
$this->fileHandle,
array_keys(SomeModel::lineMapping(SomeModel::first())),
self::DELIMITER
);

$this->headerWritten = true;
}

fputcsv(
$this->fileHandle,
SomeModel::lineMapping($line),
self::DELIMITER
);
}
}


And finally the third and last layer: this is responsible for opening and delivering the file. So we have Sftp and Response in this layer.



class SftpCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://temp', 'w');
}

protected function deliver($filename)
{
return Storage::disk('sftp')->put($filename, $this->fileHandle);
}
}

class ResponseCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://output', 'w');
}

protected function deliver($filename)
{
$handle = $this->fileHandle;

return new StreamedResponse(function () use ($handle, $filename) {
// I don't know this StreamedResponse so you might want to correct this
// the only thing left is to close the handle before send the file
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}





share|improve this answer























  • I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
    – cbaconnier
    Nov 18 at 10:28












  • By the way, why the downvote?
    – cbaconnier
    2 days ago










  • @cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
    – Edson Horacio Junior
    2 days ago










  • Take a look, now there's real code @cbaconnier
    – Edson Horacio Junior
    yesterday






  • 1




    Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
    – cbaconnier
    yesterday


















up vote
1
down vote













Responding to your statement




As I understand I should have one class that handle the CSV, one that handle the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case).




Perhaps by the word class you mean method, as it seems you have the following methods:




  • exportCSVFileToSftp

  • exportCSVFileToResponse

  • buildCsv

  • lineMapping


And those appear to line up with what you outlined in that statement, with the exception of the CSV building abstracted out to be called by the two methods to export the data.



I have read about the S.O.L.I.D. principles a few times and tried to keep them in mind when developing new code. However, I also stumbled upon Tony Marson's Blog post: Not-so-SOLID OO principles from 2011. Basically he refutes the abstract examples used when explaining the S.O.L.I.D. principles and questions whether the implementation tracks he uses are incorrect. He does support the MVC pattern as well as three tier architecture, which overlap but are not the same thing.



General review comments



I see lines like this:




$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);



and also




$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);



Initially my thought was that $handle was being re-assigned to the value from buildCsv() which seemed like it might return a value of a different type. Then I looked at the implementation of that method and noticed that it merely returns $handle. There doesn't really seem like much advantage to re-assign the value after the CSV data is sent to the output resource...





While the method names are quite descriptive, it would be helpful for anyone reading your code to have docblocks above each method, describing the parameters, return values, etc. While there isn't one single standard for the format, there are very popular formats like phpDocumenter.



And if you are using PHP 7, you could utilize Return type declarations in order to also improve readability.





The method exportCSVFileToResponse() appears to call fclose() on the resource but exportCSVFileToSftp() does not call fclose(). As answers to Why do I need fclose after writing to a file in PHP? explain it is a good habit to always call fclose() manually for reasons of security, memory usage, knowledge about write failure, etc.






share|improve this answer



















  • 1




    I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
    – cbaconnier
    yesterday











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});






cbaconnier is a new contributor. Be nice, and check out our Code of Conduct.










 

draft saved


draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207704%2flaravel-export-customer-record-as-csv%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted
+50










Try to think about this in a more generic way, you basically have:




  1. Select data from DB

  2. Put that data in a CSV (will you in the future want to use other formats like XLSX, etc? If so, we should abstract this step too, but I wont do this right now)

  3. Return that CSV file (doesn't matter to who you're returning)


So we can create a few classes from those steps




  1. Any class that can query the DB, your app probably has a bunch of them

  2. AbstractWriter - this class is an abstraction/interface to write anywhere, you'll need to implement it for specific formats, like Writer/Csv, Writer/Xlsx, Writer/JSON, etc.

  3. AbstractFileReturner - this class is an abstraction/interface to return it to anyone, you'll need to implement it for specific cases, AbstractFileReturner/like Sftp, AbstractFileReturner/HttpResponse, etc.


Any time you need more File formats or Returner, you just implement the AbstractWriter or FileReturner respectively one more time.



Doing all these, you'll be using Single Responsability Principle (each class do only one thing) and Open/Closed (the abstractions/interfaces are closed for modification, but open for extension).





Real code



Ok so I came up with a little bit of code having SOLID in mind and to make it easy to add more file formats and and way outs (Sftp, Response, etc).



This is a photo of the layers I tried to create, it might make the code more understandable.
enter image description here



Now embrace yourself for there are coming a few classes.



I didn't test this code and doens't know much of Laravel, so there might be a few bugs, but you'll get the big picture.



First, a sample controller and model which I thought was your Line.



class SomeModel
{
// the chunk does not go here, it will go inside who's putting the data into the csv
public static function someQuery()
{
return Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
});
}

public static function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}

class SomeController
{
public function exportCsvSftpAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output.csv';

$exporter = new SftpCsvExporter($dataCollection, $filename);
$exporter->export();
}

public function exportCsvResponseAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output2.csv';

$exporter = new ResponseCsvExporter($dataCollection, $filename);
$exporter->export();
}
}


Than there's the first layer: Export, I created an interface to say what it should do (don't mind about how now) and a abstract class that implements some parts of it.



interface Exportable
{
protected function createFile();

protected function write($line);

protected function deliver($filename);
}

abstract class Export implements Exportable
{
protected $fileHandle = null;
private $dataCollection;
private $filename;

public function __construct($dataCollection, $filename)
{
$this->createFile();

// @TODO You should add validation for $dataCollection like
// checking if it has data, if it really is a Collection so we can call chunk, etc
// And check $filename for invalid characters
$this->dataCollection = $dataCollection;
$this->filename = $filename;
}

protected abstract function createFile();

protected abstract function write($line);

protected abstract function deliver($filename);

// this method is who makes it all work together
// and let the controller be so simple
public function export()
{
$this->dataCollection->chunk(200, function ($lines) {
foreach ($lines as $line) {
$this->write($line);
}
});

$this->deliver($this->filename);
}
}


Now there's the second layer: Csv, in this layer you will be able to add more file formats in the future if needed.

This layer knows how to write, but doesn't know how to open nor deliver the file.



abstract class CsvExporter extends Export
{
const DELIMITER = ';';

private $headerWritten = false;

protected function write($line)
{
if (!$this->headerWritten) {
fputcsv(
$this->fileHandle,
array_keys(SomeModel::lineMapping(SomeModel::first())),
self::DELIMITER
);

$this->headerWritten = true;
}

fputcsv(
$this->fileHandle,
SomeModel::lineMapping($line),
self::DELIMITER
);
}
}


And finally the third and last layer: this is responsible for opening and delivering the file. So we have Sftp and Response in this layer.



class SftpCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://temp', 'w');
}

protected function deliver($filename)
{
return Storage::disk('sftp')->put($filename, $this->fileHandle);
}
}

class ResponseCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://output', 'w');
}

protected function deliver($filename)
{
$handle = $this->fileHandle;

return new StreamedResponse(function () use ($handle, $filename) {
// I don't know this StreamedResponse so you might want to correct this
// the only thing left is to close the handle before send the file
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}





share|improve this answer























  • I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
    – cbaconnier
    Nov 18 at 10:28












  • By the way, why the downvote?
    – cbaconnier
    2 days ago










  • @cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
    – Edson Horacio Junior
    2 days ago










  • Take a look, now there's real code @cbaconnier
    – Edson Horacio Junior
    yesterday






  • 1




    Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
    – cbaconnier
    yesterday















up vote
2
down vote



accepted
+50










Try to think about this in a more generic way, you basically have:




  1. Select data from DB

  2. Put that data in a CSV (will you in the future want to use other formats like XLSX, etc? If so, we should abstract this step too, but I wont do this right now)

  3. Return that CSV file (doesn't matter to who you're returning)


So we can create a few classes from those steps




  1. Any class that can query the DB, your app probably has a bunch of them

  2. AbstractWriter - this class is an abstraction/interface to write anywhere, you'll need to implement it for specific formats, like Writer/Csv, Writer/Xlsx, Writer/JSON, etc.

  3. AbstractFileReturner - this class is an abstraction/interface to return it to anyone, you'll need to implement it for specific cases, AbstractFileReturner/like Sftp, AbstractFileReturner/HttpResponse, etc.


Any time you need more File formats or Returner, you just implement the AbstractWriter or FileReturner respectively one more time.



Doing all these, you'll be using Single Responsability Principle (each class do only one thing) and Open/Closed (the abstractions/interfaces are closed for modification, but open for extension).





Real code



Ok so I came up with a little bit of code having SOLID in mind and to make it easy to add more file formats and and way outs (Sftp, Response, etc).



This is a photo of the layers I tried to create, it might make the code more understandable.
enter image description here



Now embrace yourself for there are coming a few classes.



I didn't test this code and doens't know much of Laravel, so there might be a few bugs, but you'll get the big picture.



First, a sample controller and model which I thought was your Line.



class SomeModel
{
// the chunk does not go here, it will go inside who's putting the data into the csv
public static function someQuery()
{
return Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
});
}

public static function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}

class SomeController
{
public function exportCsvSftpAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output.csv';

$exporter = new SftpCsvExporter($dataCollection, $filename);
$exporter->export();
}

public function exportCsvResponseAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output2.csv';

$exporter = new ResponseCsvExporter($dataCollection, $filename);
$exporter->export();
}
}


Than there's the first layer: Export, I created an interface to say what it should do (don't mind about how now) and a abstract class that implements some parts of it.



interface Exportable
{
protected function createFile();

protected function write($line);

protected function deliver($filename);
}

abstract class Export implements Exportable
{
protected $fileHandle = null;
private $dataCollection;
private $filename;

public function __construct($dataCollection, $filename)
{
$this->createFile();

// @TODO You should add validation for $dataCollection like
// checking if it has data, if it really is a Collection so we can call chunk, etc
// And check $filename for invalid characters
$this->dataCollection = $dataCollection;
$this->filename = $filename;
}

protected abstract function createFile();

protected abstract function write($line);

protected abstract function deliver($filename);

// this method is who makes it all work together
// and let the controller be so simple
public function export()
{
$this->dataCollection->chunk(200, function ($lines) {
foreach ($lines as $line) {
$this->write($line);
}
});

$this->deliver($this->filename);
}
}


Now there's the second layer: Csv, in this layer you will be able to add more file formats in the future if needed.

This layer knows how to write, but doesn't know how to open nor deliver the file.



abstract class CsvExporter extends Export
{
const DELIMITER = ';';

private $headerWritten = false;

protected function write($line)
{
if (!$this->headerWritten) {
fputcsv(
$this->fileHandle,
array_keys(SomeModel::lineMapping(SomeModel::first())),
self::DELIMITER
);

$this->headerWritten = true;
}

fputcsv(
$this->fileHandle,
SomeModel::lineMapping($line),
self::DELIMITER
);
}
}


And finally the third and last layer: this is responsible for opening and delivering the file. So we have Sftp and Response in this layer.



class SftpCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://temp', 'w');
}

protected function deliver($filename)
{
return Storage::disk('sftp')->put($filename, $this->fileHandle);
}
}

class ResponseCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://output', 'w');
}

protected function deliver($filename)
{
$handle = $this->fileHandle;

return new StreamedResponse(function () use ($handle, $filename) {
// I don't know this StreamedResponse so you might want to correct this
// the only thing left is to close the handle before send the file
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}





share|improve this answer























  • I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
    – cbaconnier
    Nov 18 at 10:28












  • By the way, why the downvote?
    – cbaconnier
    2 days ago










  • @cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
    – Edson Horacio Junior
    2 days ago










  • Take a look, now there's real code @cbaconnier
    – Edson Horacio Junior
    yesterday






  • 1




    Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
    – cbaconnier
    yesterday













up vote
2
down vote



accepted
+50







up vote
2
down vote



accepted
+50




+50




Try to think about this in a more generic way, you basically have:




  1. Select data from DB

  2. Put that data in a CSV (will you in the future want to use other formats like XLSX, etc? If so, we should abstract this step too, but I wont do this right now)

  3. Return that CSV file (doesn't matter to who you're returning)


So we can create a few classes from those steps




  1. Any class that can query the DB, your app probably has a bunch of them

  2. AbstractWriter - this class is an abstraction/interface to write anywhere, you'll need to implement it for specific formats, like Writer/Csv, Writer/Xlsx, Writer/JSON, etc.

  3. AbstractFileReturner - this class is an abstraction/interface to return it to anyone, you'll need to implement it for specific cases, AbstractFileReturner/like Sftp, AbstractFileReturner/HttpResponse, etc.


Any time you need more File formats or Returner, you just implement the AbstractWriter or FileReturner respectively one more time.



Doing all these, you'll be using Single Responsability Principle (each class do only one thing) and Open/Closed (the abstractions/interfaces are closed for modification, but open for extension).





Real code



Ok so I came up with a little bit of code having SOLID in mind and to make it easy to add more file formats and and way outs (Sftp, Response, etc).



This is a photo of the layers I tried to create, it might make the code more understandable.
enter image description here



Now embrace yourself for there are coming a few classes.



I didn't test this code and doens't know much of Laravel, so there might be a few bugs, but you'll get the big picture.



First, a sample controller and model which I thought was your Line.



class SomeModel
{
// the chunk does not go here, it will go inside who's putting the data into the csv
public static function someQuery()
{
return Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
});
}

public static function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}

class SomeController
{
public function exportCsvSftpAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output.csv';

$exporter = new SftpCsvExporter($dataCollection, $filename);
$exporter->export();
}

public function exportCsvResponseAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output2.csv';

$exporter = new ResponseCsvExporter($dataCollection, $filename);
$exporter->export();
}
}


Than there's the first layer: Export, I created an interface to say what it should do (don't mind about how now) and a abstract class that implements some parts of it.



interface Exportable
{
protected function createFile();

protected function write($line);

protected function deliver($filename);
}

abstract class Export implements Exportable
{
protected $fileHandle = null;
private $dataCollection;
private $filename;

public function __construct($dataCollection, $filename)
{
$this->createFile();

// @TODO You should add validation for $dataCollection like
// checking if it has data, if it really is a Collection so we can call chunk, etc
// And check $filename for invalid characters
$this->dataCollection = $dataCollection;
$this->filename = $filename;
}

protected abstract function createFile();

protected abstract function write($line);

protected abstract function deliver($filename);

// this method is who makes it all work together
// and let the controller be so simple
public function export()
{
$this->dataCollection->chunk(200, function ($lines) {
foreach ($lines as $line) {
$this->write($line);
}
});

$this->deliver($this->filename);
}
}


Now there's the second layer: Csv, in this layer you will be able to add more file formats in the future if needed.

This layer knows how to write, but doesn't know how to open nor deliver the file.



abstract class CsvExporter extends Export
{
const DELIMITER = ';';

private $headerWritten = false;

protected function write($line)
{
if (!$this->headerWritten) {
fputcsv(
$this->fileHandle,
array_keys(SomeModel::lineMapping(SomeModel::first())),
self::DELIMITER
);

$this->headerWritten = true;
}

fputcsv(
$this->fileHandle,
SomeModel::lineMapping($line),
self::DELIMITER
);
}
}


And finally the third and last layer: this is responsible for opening and delivering the file. So we have Sftp and Response in this layer.



class SftpCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://temp', 'w');
}

protected function deliver($filename)
{
return Storage::disk('sftp')->put($filename, $this->fileHandle);
}
}

class ResponseCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://output', 'w');
}

protected function deliver($filename)
{
$handle = $this->fileHandle;

return new StreamedResponse(function () use ($handle, $filename) {
// I don't know this StreamedResponse so you might want to correct this
// the only thing left is to close the handle before send the file
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}





share|improve this answer














Try to think about this in a more generic way, you basically have:




  1. Select data from DB

  2. Put that data in a CSV (will you in the future want to use other formats like XLSX, etc? If so, we should abstract this step too, but I wont do this right now)

  3. Return that CSV file (doesn't matter to who you're returning)


So we can create a few classes from those steps




  1. Any class that can query the DB, your app probably has a bunch of them

  2. AbstractWriter - this class is an abstraction/interface to write anywhere, you'll need to implement it for specific formats, like Writer/Csv, Writer/Xlsx, Writer/JSON, etc.

  3. AbstractFileReturner - this class is an abstraction/interface to return it to anyone, you'll need to implement it for specific cases, AbstractFileReturner/like Sftp, AbstractFileReturner/HttpResponse, etc.


Any time you need more File formats or Returner, you just implement the AbstractWriter or FileReturner respectively one more time.



Doing all these, you'll be using Single Responsability Principle (each class do only one thing) and Open/Closed (the abstractions/interfaces are closed for modification, but open for extension).





Real code



Ok so I came up with a little bit of code having SOLID in mind and to make it easy to add more file formats and and way outs (Sftp, Response, etc).



This is a photo of the layers I tried to create, it might make the code more understandable.
enter image description here



Now embrace yourself for there are coming a few classes.



I didn't test this code and doens't know much of Laravel, so there might be a few bugs, but you'll get the big picture.



First, a sample controller and model which I thought was your Line.



class SomeModel
{
// the chunk does not go here, it will go inside who's putting the data into the csv
public static function someQuery()
{
return Line::with(['invoice', 'invoice.customer', 'item'])
->whereHas('invoice', function ($query) {
$query->where('is_exportable', 1);
});
}

public static function lineMapping(Line $line)
{
return [
'Invoice number' => $line->invoice->id,
'Document type' => self::DOCUMENT_TYPE,
'Date' => $line->invoice->date,
];
}
}

class SomeController
{
public function exportCsvSftpAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output.csv';

$exporter = new SftpCsvExporter($dataCollection, $filename);
$exporter->export();
}

public function exportCsvResponseAction()
{
$dataCollection = SomeModel::someQuery();
$filename = 'output2.csv';

$exporter = new ResponseCsvExporter($dataCollection, $filename);
$exporter->export();
}
}


Than there's the first layer: Export, I created an interface to say what it should do (don't mind about how now) and a abstract class that implements some parts of it.



interface Exportable
{
protected function createFile();

protected function write($line);

protected function deliver($filename);
}

abstract class Export implements Exportable
{
protected $fileHandle = null;
private $dataCollection;
private $filename;

public function __construct($dataCollection, $filename)
{
$this->createFile();

// @TODO You should add validation for $dataCollection like
// checking if it has data, if it really is a Collection so we can call chunk, etc
// And check $filename for invalid characters
$this->dataCollection = $dataCollection;
$this->filename = $filename;
}

protected abstract function createFile();

protected abstract function write($line);

protected abstract function deliver($filename);

// this method is who makes it all work together
// and let the controller be so simple
public function export()
{
$this->dataCollection->chunk(200, function ($lines) {
foreach ($lines as $line) {
$this->write($line);
}
});

$this->deliver($this->filename);
}
}


Now there's the second layer: Csv, in this layer you will be able to add more file formats in the future if needed.

This layer knows how to write, but doesn't know how to open nor deliver the file.



abstract class CsvExporter extends Export
{
const DELIMITER = ';';

private $headerWritten = false;

protected function write($line)
{
if (!$this->headerWritten) {
fputcsv(
$this->fileHandle,
array_keys(SomeModel::lineMapping(SomeModel::first())),
self::DELIMITER
);

$this->headerWritten = true;
}

fputcsv(
$this->fileHandle,
SomeModel::lineMapping($line),
self::DELIMITER
);
}
}


And finally the third and last layer: this is responsible for opening and delivering the file. So we have Sftp and Response in this layer.



class SftpCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://temp', 'w');
}

protected function deliver($filename)
{
return Storage::disk('sftp')->put($filename, $this->fileHandle);
}
}

class ResponseCsvExporter extends CsvExporter
{
protected function createFile()
{
if ($this->fileHandle !== null) {
throw new LogicException('Handle already initilized');
}

$this->fileHandle = fopen('php://output', 'w');
}

protected function deliver($filename)
{
$handle = $this->fileHandle;

return new StreamedResponse(function () use ($handle, $filename) {
// I don't know this StreamedResponse so you might want to correct this
// the only thing left is to close the handle before send the file
fclose($handle);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
]);
}
}






share|improve this answer














share|improve this answer



share|improve this answer








edited yesterday

























answered Nov 17 at 18:25









Edson Horacio Junior

20817




20817












  • I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
    – cbaconnier
    Nov 18 at 10:28












  • By the way, why the downvote?
    – cbaconnier
    2 days ago










  • @cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
    – Edson Horacio Junior
    2 days ago










  • Take a look, now there's real code @cbaconnier
    – Edson Horacio Junior
    yesterday






  • 1




    Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
    – cbaconnier
    yesterday


















  • I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
    – cbaconnier
    Nov 18 at 10:28












  • By the way, why the downvote?
    – cbaconnier
    2 days ago










  • @cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
    – Edson Horacio Junior
    2 days ago










  • Take a look, now there's real code @cbaconnier
    – Edson Horacio Junior
    yesterday






  • 1




    Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
    – cbaconnier
    yesterday
















I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
– cbaconnier
Nov 18 at 10:28






I already understand this. But in practice, when I'm looking the code code and tri to dissassemble them, I Just don't understand how.
– cbaconnier
Nov 18 at 10:28














By the way, why the downvote?
– cbaconnier
2 days ago




By the way, why the downvote?
– cbaconnier
2 days ago












@cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
– Edson Horacio Junior
2 days ago




@cbaconnier I'm going to edit this answer with the practice code. The downvote wasn't mine
– Edson Horacio Junior
2 days ago












Take a look, now there's real code @cbaconnier
– Edson Horacio Junior
yesterday




Take a look, now there's real code @cbaconnier
– Edson Horacio Junior
yesterday




1




1




Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
– cbaconnier
yesterday




Thank you so much!! That's truly helping me. For someone who doesn't know much about Laravel, you're doing very well!
– cbaconnier
yesterday












up vote
1
down vote













Responding to your statement




As I understand I should have one class that handle the CSV, one that handle the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case).




Perhaps by the word class you mean method, as it seems you have the following methods:




  • exportCSVFileToSftp

  • exportCSVFileToResponse

  • buildCsv

  • lineMapping


And those appear to line up with what you outlined in that statement, with the exception of the CSV building abstracted out to be called by the two methods to export the data.



I have read about the S.O.L.I.D. principles a few times and tried to keep them in mind when developing new code. However, I also stumbled upon Tony Marson's Blog post: Not-so-SOLID OO principles from 2011. Basically he refutes the abstract examples used when explaining the S.O.L.I.D. principles and questions whether the implementation tracks he uses are incorrect. He does support the MVC pattern as well as three tier architecture, which overlap but are not the same thing.



General review comments



I see lines like this:




$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);



and also




$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);



Initially my thought was that $handle was being re-assigned to the value from buildCsv() which seemed like it might return a value of a different type. Then I looked at the implementation of that method and noticed that it merely returns $handle. There doesn't really seem like much advantage to re-assign the value after the CSV data is sent to the output resource...





While the method names are quite descriptive, it would be helpful for anyone reading your code to have docblocks above each method, describing the parameters, return values, etc. While there isn't one single standard for the format, there are very popular formats like phpDocumenter.



And if you are using PHP 7, you could utilize Return type declarations in order to also improve readability.





The method exportCSVFileToResponse() appears to call fclose() on the resource but exportCSVFileToSftp() does not call fclose(). As answers to Why do I need fclose after writing to a file in PHP? explain it is a good habit to always call fclose() manually for reasons of security, memory usage, knowledge about write failure, etc.






share|improve this answer



















  • 1




    I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
    – cbaconnier
    yesterday















up vote
1
down vote













Responding to your statement




As I understand I should have one class that handle the CSV, one that handle the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case).




Perhaps by the word class you mean method, as it seems you have the following methods:




  • exportCSVFileToSftp

  • exportCSVFileToResponse

  • buildCsv

  • lineMapping


And those appear to line up with what you outlined in that statement, with the exception of the CSV building abstracted out to be called by the two methods to export the data.



I have read about the S.O.L.I.D. principles a few times and tried to keep them in mind when developing new code. However, I also stumbled upon Tony Marson's Blog post: Not-so-SOLID OO principles from 2011. Basically he refutes the abstract examples used when explaining the S.O.L.I.D. principles and questions whether the implementation tracks he uses are incorrect. He does support the MVC pattern as well as three tier architecture, which overlap but are not the same thing.



General review comments



I see lines like this:




$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);



and also




$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);



Initially my thought was that $handle was being re-assigned to the value from buildCsv() which seemed like it might return a value of a different type. Then I looked at the implementation of that method and noticed that it merely returns $handle. There doesn't really seem like much advantage to re-assign the value after the CSV data is sent to the output resource...





While the method names are quite descriptive, it would be helpful for anyone reading your code to have docblocks above each method, describing the parameters, return values, etc. While there isn't one single standard for the format, there are very popular formats like phpDocumenter.



And if you are using PHP 7, you could utilize Return type declarations in order to also improve readability.





The method exportCSVFileToResponse() appears to call fclose() on the resource but exportCSVFileToSftp() does not call fclose(). As answers to Why do I need fclose after writing to a file in PHP? explain it is a good habit to always call fclose() manually for reasons of security, memory usage, knowledge about write failure, etc.






share|improve this answer



















  • 1




    I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
    – cbaconnier
    yesterday













up vote
1
down vote










up vote
1
down vote









Responding to your statement




As I understand I should have one class that handle the CSV, one that handle the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case).




Perhaps by the word class you mean method, as it seems you have the following methods:




  • exportCSVFileToSftp

  • exportCSVFileToResponse

  • buildCsv

  • lineMapping


And those appear to line up with what you outlined in that statement, with the exception of the CSV building abstracted out to be called by the two methods to export the data.



I have read about the S.O.L.I.D. principles a few times and tried to keep them in mind when developing new code. However, I also stumbled upon Tony Marson's Blog post: Not-so-SOLID OO principles from 2011. Basically he refutes the abstract examples used when explaining the S.O.L.I.D. principles and questions whether the implementation tracks he uses are incorrect. He does support the MVC pattern as well as three tier architecture, which overlap but are not the same thing.



General review comments



I see lines like this:




$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);



and also




$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);



Initially my thought was that $handle was being re-assigned to the value from buildCsv() which seemed like it might return a value of a different type. Then I looked at the implementation of that method and noticed that it merely returns $handle. There doesn't really seem like much advantage to re-assign the value after the CSV data is sent to the output resource...





While the method names are quite descriptive, it would be helpful for anyone reading your code to have docblocks above each method, describing the parameters, return values, etc. While there isn't one single standard for the format, there are very popular formats like phpDocumenter.



And if you are using PHP 7, you could utilize Return type declarations in order to also improve readability.





The method exportCSVFileToResponse() appears to call fclose() on the resource but exportCSVFileToSftp() does not call fclose(). As answers to Why do I need fclose after writing to a file in PHP? explain it is a good habit to always call fclose() manually for reasons of security, memory usage, knowledge about write failure, etc.






share|improve this answer














Responding to your statement




As I understand I should have one class that handle the CSV, one that handle the sFTP, one for the Response, and one to handle the logic of the model (the mapping in my case).




Perhaps by the word class you mean method, as it seems you have the following methods:




  • exportCSVFileToSftp

  • exportCSVFileToResponse

  • buildCsv

  • lineMapping


And those appear to line up with what you outlined in that statement, with the exception of the CSV building abstracted out to be called by the two methods to export the data.



I have read about the S.O.L.I.D. principles a few times and tried to keep them in mind when developing new code. However, I also stumbled upon Tony Marson's Blog post: Not-so-SOLID OO principles from 2011. Basically he refutes the abstract examples used when explaining the S.O.L.I.D. principles and questions whether the implementation tracks he uses are incorrect. He does support the MVC pattern as well as three tier architecture, which overlap but are not the same thing.



General review comments



I see lines like this:




$handle = fopen('php://temp', 'w');
$handle = $this->buildCsv($handle);



and also




$handle = fopen('php://output', 'w');
$handle = $this->buildCsv($handle);



Initially my thought was that $handle was being re-assigned to the value from buildCsv() which seemed like it might return a value of a different type. Then I looked at the implementation of that method and noticed that it merely returns $handle. There doesn't really seem like much advantage to re-assign the value after the CSV data is sent to the output resource...





While the method names are quite descriptive, it would be helpful for anyone reading your code to have docblocks above each method, describing the parameters, return values, etc. While there isn't one single standard for the format, there are very popular formats like phpDocumenter.



And if you are using PHP 7, you could utilize Return type declarations in order to also improve readability.





The method exportCSVFileToResponse() appears to call fclose() on the resource but exportCSVFileToSftp() does not call fclose(). As answers to Why do I need fclose after writing to a file in PHP? explain it is a good habit to always call fclose() manually for reasons of security, memory usage, knowledge about write failure, etc.







share|improve this answer














share|improve this answer



share|improve this answer








edited yesterday

























answered yesterday









Sᴀᴍ Onᴇᴌᴀ

7,71061748




7,71061748








  • 1




    I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
    – cbaconnier
    yesterday














  • 1




    I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
    – cbaconnier
    yesterday








1




1




I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
– cbaconnier
yesterday




I did't had the time to read Tony Marson's blog today. For sure, I will! Thanks! But In my scenario, I have to decouple this class since I need to reuse the components elsewhere. I also keep in mind your comments about fclose and phpdoc(which I generaly do). I made buildCsv() for the readability, but I know it demonstrate a weak code.
– cbaconnier
yesterday










cbaconnier is a new contributor. Be nice, and check out our Code of Conduct.










 

draft saved


draft discarded


















cbaconnier is a new contributor. Be nice, and check out our Code of Conduct.













cbaconnier is a new contributor. Be nice, and check out our Code of Conduct.












cbaconnier is a new contributor. Be nice, and check out our Code of Conduct.















 


draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207704%2flaravel-export-customer-record-as-csv%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Сан-Квентин

8-я гвардейская общевойсковая армия

Алькесар