{"id":2439,"date":"2015-08-26T17:58:27","date_gmt":"2015-08-26T15:58:27","guid":{"rendered":"http:\/\/nyamsprod.com\/blog\/?p=2439"},"modified":"2015-08-27T09:02:02","modified_gmt":"2015-08-27T07:02:02","slug":"qa-enforcing-enclosure-with-leaguecsv","status":"publish","type":"post","link":"https:\/\/nyamsprod.com\/blog\/qa-enforcing-enclosure-with-leaguecsv\/","title":{"rendered":"Q&#038;A: Enforcing enclosure with League\\Csv"},"content":{"rendered":"<p>It is common knowledge that PHP&#8217;s <code>fputcsv<\/code> function <a href=\"http:\/\/stackoverflow.com\/questions\/2489553\/forcing-fputcsv-to-use-enclosure-for-all-fields\" target=\"_blank\">does not allow enforcing the enclosure on every field<\/a>.\u00a0 Using\u00a0 League CSV and PHP stream filter features\u00a0 let me show you how to do so step by step.<!--more--><\/p>\n<h3>1 &#8211; Install league csv<\/h3>\n<p>if you don&#8217;t already have it around, you can install the latest stable version using <code>Composer<\/code>.<\/p>\n<pre id=\"snippet_enclosure_install_league_csv\"><code class=\"language-javascript\">$ composer require league\/csv<\/code><\/pre>\n<h3>2 &#8211; Choose a sequence to enforce the presence of the enclosure character<\/h3>\n<p>According to <a href=\"https:\/\/github.com\/php\/php-src\/blob\/master\/ext\/standard\/file.c#L1879\" target=\"_blank\">PHP fputcsv source code<\/a> the enclosure is added only under the following circumstances:<\/p>\n<ul>\n<li>one of the CSV control character is present in the field (delimiter, enclosure or escape character);<\/li>\n<li>the newline or the tab character are present in the field <code>\\n<\/code>, <code>\\r<\/code>, <code>\\t<\/code><\/li>\n<li>the space character is present<\/li>\n<\/ul>\n<p>In my example I&#8217;m going to use the tab character and another <em>exotic<\/em> character.<\/p>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=sequence.php\"><\/script>\n<h3>3 &#8211; Set up you CSV<\/h3>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=writer-setup.php\"><\/script>\n<h3>4 &#8211; Enforce the sequence on every CSV field<\/h3>\n<p>Using <a href=\"http:\/\/csv.thephpleague.com\/inserting\/#row-formatting\" target=\"_blank\">the formatting capabilities of the Writer object<\/a> you can easily add the sequence to each CSV field<\/p>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=formatter.php\"><\/script>\n<p>Every time you add a row with the Writer class prior from being added the row will be prepended with the <code>$sequence<\/code> sequence.<\/p>\n<h3>5 &#8211; Create a stream filter<\/h3>\n<p>This is were <em>the magic happens.<\/em> One of the overlooked feature of the package is that <a href=\"http:\/\/csv.thephpleague.com\/filtering\/\" target=\"_blank\">it can use stream filters to enhance CSV alteration<\/a>. <strong>A stream filter can modify your CSV content after PHP&#8217;s <code>fputcsv<\/code> has formatted your row but before the content is actually save to the file<\/strong>.<\/p>\n<p>I&#8217;ve created a stream filter class by extending PHP&#8217;s <code>php_user_filter<\/code> class. You can view the source code via the following gist .<\/p>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=RemoveSequence.php\"><\/script>\n<p>The main code is in the <code>RemoveSequence::filter<\/code> method. What this code does is basically removing the added sequence that enforces the enclosure character from the CSV content prior to it being saved to the file but after PHP <code>fputcsv<\/code>.<\/p>\n<h3>6 &#8211; Attach the stream filter to the Writer object<\/h3>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=register.php\"><\/script>\n<ul>\n<li><code>registerStreamFilter<\/code>: is a static method to ease registering the class as a possible stream filter;<\/li>\n<li><code>createFilterName<\/code>: is a static method to ease generating the stream filtername to attach to the Writer object according to the CSV control characters and the added sequence;<\/li>\n<\/ul>\n<p>To apply a valid and efficient filter you are required to choose a sequence which is:<\/p>\n<ul>\n<li>unique;<\/li>\n<li>does not contain any newline character;<\/li>\n<li>does not contain any space or null byte character;<\/li>\n<li>does not contain an already used CSV control character;<\/li>\n<\/ul>\n<p>This is the reason why I choose the tab character. The extra character is added in case your data already contains a tab character to make your added sequence truly unique. The enforce sequence must be unique and as small as possible too.<\/p>\n<h3>7 &#8211; Create you CSV<\/h3>\n<p>Now that all the pieces are in place let&#8217;s create the CSV row<\/p>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=write.php\"><\/script>\n<h3>Conclusion<\/h3>\n<p>Et voila! You&#8217;ve created a CSV with enforced enclosure on everyone of its field. Let&#8217;s recap it all:<\/p>\n<script src=\"https:\/\/gist.github.com\/6cb02e92e4820093b782.js?file=script.php\"><\/script>\n<h3>Footnotes<\/h3>\n<p>Of note, In the recap script I&#8217;ve added <a href=\"http:\/\/csv.thephpleague.com\/inserting\/#row-validation\" target=\"_blank\">an extra validation step<\/a> to illustrate another feature of the <code>League\\Csv\\Writer<\/code> object which is to validate a row prior to its insertion according to your own rules. If a cell is invalid a <code>League\\Csv\\Exception\\InvalidRowException<\/code> expection is thrown.<\/p>\n<p>As you may have guess it, the formatting and the validation steps are optional. If you already have formatted and validated your data prior to the insertion, you don&#8217;t need them.<br \/>\nConversely,\u00a0 the addition of the sequence before insertion and its subsequent subtraction using a stream filter is required.<\/p>\n<p>Registering the stream filter can be done in the script or in the bootstrap script of your application. You should not register the stream filter prior to each usage, but your are required to attach it to the <code>League\\Csv\\Writer<\/code> object in order to use it.<\/p>\n<p>This technique works even without the league CSV library as long as you are able to manipulate your CSV as a stream on insertion.<\/p>\n<p>In case of the League CSV there are some trade-offs when using this technique:<\/p>\n<ul>\n<li>Its usage is limited by how <code>SplFileObject<\/code> supports stream filters;<\/li>\n<li><code>League\\Csv\\Writer<\/code> insertion speed is slowed down because of\u00a0 the extra steps you are adding;<\/li>\n<\/ul>\n<h3>Last but not least<\/h3>\n<p>The League\\Csv is an open source project with a MIT License so contributions are more than welcome and will be fully credited.\u00a0 These contributions can be anything from reporting an issue, requesting or adding missing features or simply improving or correcting some typo on the documentation website.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to enforce the enclosure character on every field of a CSV in PHP using PHP&#8217;s stream filter features<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[588],"tags":[761,788,194,789,412,773],"class_list":["post-2439","post","type-post","status-publish","format-standard","hentry","category-humeurs","tag-csv","tag-enclosure","tag-filter","tag-fputcsv","tag-php","tag-stream"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/posts\/2439","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/comments?post=2439"}],"version-history":[{"count":5,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/posts\/2439\/revisions"}],"predecessor-version":[{"id":2472,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/posts\/2439\/revisions\/2472"}],"wp:attachment":[{"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/media?parent=2439"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/categories?post=2439"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nyamsprod.com\/blog\/wp-json\/wp\/v2\/tags?post=2439"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}