https://fluffyandflakey.blog/2015/10/22/functional-php/ Skip to content Fluffy and Flakey Blogging about cooking, engineering, programming, and more... Menu * Home * Insightful Conversations * Quirks * On Programming * HTML + Quirks and Tips + Parsing Hall of Fame + History and Motivation October 22, 2015Dennis Snell1 Comment on Functional PHP Functional PHP Over the course of the past week, I had a chance to write some brand new PHP code and I took the opportunity to try writing in a (mostly) purely functional style. The experience turned out to be very pleasant and definitely helped us to accomplish our goals as we figured out the details and changes the project needed. A different approach * Instead of fearing the overhead in PHP for function calls, I tried to split everything out into separate functions: one responsibility per function. * Operations were performed immutably, though this didn't have a big impact on the code. Practically speaking, this meant a preference for `map`, `reduce`, and `filter` operations which also eliminate off-by-one and null reference errors. * As I wrote the functions, I focussed on composability and modularity so that I could move the pieces around without making big changes elsewhere. Different results * Unlike my normal code, these programs ended up with a large flat array of functions. I normally prefer to have fewer functions and more order, but this level of unitized functionality ended up being quite the boon, making changes to program behavior effortless. In the few cases we needed to modify what the program did or fix a design bug, it was usually around a couple of lines of changes, mostly moving function calls into different orders. * There are relatively few conditionals and very few variables in the code. For the most part, there are only variables where PHP forces me to use them to accomplish my goal, such as in obtaining the output from calling a process with `exec()`. Breaking down the functions has this effect. First of all, because the functions are often the callbacks for mapping operations, there's no need to check if the passed-in arguments exist, which I would normally have to do if they were passed an array instead. Secondly, because the functions actually perform such a specific task, there's little need to store the intermediate state within. * There are zero errors from PHP Mess Detector and PHP Code Sniffer. The entire file has a Cycolmatic Complexity of 9 for 186 lines and a maintainability index of 67. I think it would be higher if it were commented more, but the code itself is fairly well self-documenting. Update: By adding PHPDoc comments, I was able to boost the maintainability index from 67 to 83, proving that there's always a way to game the system. If you examine the code, you can judge for yourself how necessary they are. Conclusion Like I mentioned above, modifying this (and another) program was incredibly easy. Taking the time up-front to define the interface certainly helped me to be able to make this do almost exactly what it needed to the first time I had it "finished." Because the functions were so small, it was simple to gauge whether or not they would work as expected, then it was just a matter of stringing them together. Like a well-tested project, this one made it a breeze to find the spot of the one bug we found, preventing me from spending so much time hunting it down. The functional approach definitely lends itself well to building reasonable, effective, and comprehensive tests. It was so easy working with it at times that it kind of made me all tingly inside. This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters #!/usr/local/bin/php true, 'ci' => true ] ); }; $files = $names; $files = array_filter( $files, $isIrrelevant ); $files = array_filter( $files, 'file_exists' ); return $files; } /** * Returns only lines that start with the specified character. * * @param string $type The letter describing which svn operation is desired * @return Closure produces a bool output for the passed line if it starts with $type */ function typeFilter( $type ) { return function( $line ) use ( $type ) { return $type === substr( $line, 0, 1 ); }; } /** * Converts the `svn status` output line into a filename * * @param string $line one line of output from `svn status` * @return string Trimmed version of $line without the status info prefix */ function statusLineToFilename( $line ) { return trim( substr( $line, 7 ) ); } /** * Returns an array of files of a specified type * * @param Array $lines lines of output from `svn status` * @param string $type Which type of `svn` operation to return * @return Array Filenames for files that exist and match the $type predicate */ function filesByType( $lines, $type ) { return array_values( array_map( 'statusLineToFilename', array_filter ( $lines, typeFilter( $type ) ) ) ); } /** * Splits the output of `svn status` into a structured * object with added, modified, missing, and deleted files * as arrays. * * @param Array $lines output lines from `svn status` * @return stdClass Structured view of changed files */ function statusLinesToChangeSet( $lines ) { return (object) [ "addedFiles" => array_merge( filesByType( $lines, 'A' ), filesByType( $lines, 'R' ) ), "deletedFiles" => filesByType( $lines, 'D' ), "missingFiles" => filesByType( $lines, '!' ), "modifiedFiles" => filesByType( $lines, 'M' ) ]; } /** * Computes the changeset between two revisions * * @param int $revision Specific revision to examine * @return Array output from svn command */ function statusAcrossRevisions( $revision ) { if ( empty( $revision ) ) { $revision = getCurrentRevision(); } exec( sprintf( 'svn diff -c%d -summarize', $revision ), $output, $exitStatus ); if ( 0 !== $exitStatus ) { exit( $exitStatus ); } return $output; } /** * Computes the changeset assuming we are basing the * comparison off of the local working copy. * * @param Array $filenames possible list of filenames to only examine * @return Array output from svn command */ function statusForWorkingCopy( $filenames ) { exec( sprintf( 'svn status %s 2>/dev/null', implode( ' ', array_map( 'escapeshellarg', $filenames ) ) ), $output, $exitStatus ); if ( 0 !== $exitStatus ) { exit( $exitStatus ); } return $output; } /** * Returns the current revision number for the checked-out * copy of the svn repository. * * @return int Revision of current local working svn repository */ function getCurrentRevision() { exec( 'svn info | grep "Revision" | awk \'{print $2}\'', $ raw_revision ); return (int) array_shift( $raw_revision ); } /** * Returns the output of `svn diff` across revisions * * @param int $revision A specific svn revision to examine * @return Array output from svn command */ function createRevisionDiff( $revision ) { if ( empty( $revision ) ) { $revision = getCurrentRevision(); } exec( sprintf( 'svn diff -c%d 2>/dev/null', $revision ), $output, $exitStatus ); if ( 0 !== $exitStatus ) { exit( $exitStatus ); } return $output; } /** * Returns the output of `svn diff` for the working copy * * @param Array $filenames List of filenames to only examine * @return Array output from svn command */ function createWorkingCopyDiff( $filenames ) { exec( sprintf( 'svn diff %s 2>/dev/null', implode( ' ', array_map( 'escapeshellarg', $filenames ) ) ), $output, $exitStatus ); if ( 0 !== $exitStatus ) { exit( $exitStatus ); } return $output; } /** * Returns the desired `svn` diff * * @param int $revision A specific revision to examine * @param Array $filenames Specific list of files to examine only * @return string Newline-terminated list of output lines for `svn diff` */ function createDiff( $revision, $filenames ) { return implode( PHP_EOL, empty( $revision ) ? createWorkingCopyDiff( $filenames ) : createRevisionDiff( $revision ) ); } /** * Computes the desired changeset * * @param int $revision A specific revision to examine * @param Array $filenames Specific list of files to examine only * @return string JSON-encoded structured list of changed files for commit */ function listRelevantFiles( $revision, $filenames ) { return json_encode( statusLinesToChangeSet( empty( $revision ) ? statusForWorkingCopy( $filenames ) : statusAcrossRevisions( $revision ) ) ); } /** * Generates args suitable for passing into the linters * * @return int Whether or not the program was able to complete successfully */ function main() { list( $type, $revision, $filenames ) = getShellArgs(); if ( empty( $type ) ) { die( 'Please choose type of information requested: "-type changeset" or "-type diff"' ); } if ( 'changeset' === $type ) { echo listRelevantFiles( $revision, $filenames ); } if ( 'diff' === $type ) { echo createDiff( $revision, $filenames ); } echo "\n"; } main(); view raw generate-linter-args.php hosted with by GitHub Share this: * Twitter * Facebook * Email * Like this: Like Loading... [51b4eed] Published by Dennis Snell As a Code Wrangler at Automattic, Inc., I spend my days reticulating splines. As a nomad, I own very few things and enjoy living simply. I love cooking, engineering, photography, and my faithful God, Jesus Christ. View all posts by Dennis Snell programming, Uncategorized experiments, functional programming, PHP 1 thought on "Functional PHP" 1. [51b4eed892] Dennis Snell October 22, 2015 -- 3:31 am Reblogged this on Snell Family Adventures and commented: A technical post for those interested in "Functional Programming" and PHP Loading... Reply Leave a ReplyCancel reply Post navigation Previous Today I created my first monad, and it's in PHP! Next React in Five Minutes Search for: [ ] [Search] Recent Posts * UGML: A proposal to update SGML. * My WordPress Contribution Workflow * Don't convert HTML Text Encoding * On SGML: a Summary * CSV files can be UTF-8. Recent Comments How I commit to Word... on My WordPress Contribution Work... [51b4e] Dennis Snell on Typesetting Math in Gutenberg [c7d1f] Difa on Typesetting Math in Gutenberg [51b4e] Dennis Snell on Rooting out Gremlins [51b4e] Dennis Snell on Increasing Thermal Headroom Archives * October 2024 * September 2024 * July 2024 * June 2024 * March 2024 * January 2024 * December 2023 * October 2023 * December 2022 * November 2022 * September 2019 * August 2019 * April 2019 * March 2019 * September 2018 * June 2018 * September 2017 * February 2017 * November 2015 * October 2015 * September 2015 * August 2015 * July 2015 * June 2015 * May 2015 * April 2015 * March 2015 * January 2015 * April 2014 * February 2014 * December 2013 * October 2013 Categories * economics * food * gluten * Gutenberg * health * HTML * map of the day * photography * politics * privacy * programming * Quirks and Tips * security * SGML * technology * UGML * Uncategorized * WordPress Meta * Log in * Entries feed * Comments feed * WordPress.org Search for: [ ] [Search] Recent Posts * UGML: A proposal to update SGML. * My WordPress Contribution Workflow * Don't convert HTML Text Encoding * On SGML: a Summary * CSV files can be UTF-8. Recent Comments How I commit to Word... on My WordPress Contribution Work... [51b4e] Dennis Snell on Typesetting Math in Gutenberg [c7d1f] Difa on Typesetting Math in Gutenberg [51b4e] Dennis Snell on Rooting out Gremlins [51b4e] Dennis Snell on Increasing Thermal Headroom Archives * October 2024 * September 2024 * July 2024 * June 2024 * March 2024 * January 2024 * December 2023 * October 2023 * December 2022 * November 2022 * September 2019 * August 2019 * April 2019 * March 2019 * September 2018 * June 2018 * September 2017 * February 2017 * November 2015 * October 2015 * September 2015 * August 2015 * July 2015 * June 2015 * May 2015 * April 2015 * March 2015 * January 2015 * April 2014 * February 2014 * December 2013 * October 2013 Categories * economics * food * gluten * Gutenberg * health * HTML * map of the day * photography * politics * privacy * programming * Quirks and Tips * security * SGML * technology * UGML * Uncategorized * WordPress Meta * Log in * Entries feed * Comments feed * WordPress.org Powered by WordPress.com. Discover more from Fluffy and Flakey Subscribe now to keep reading and get access to the full archive. Type your email... [ ] Subscribe Continue reading %d