├── .editorconfig
├── .github
└── workflows
│ └── github-pages.yml
├── .gitignore
├── README.md
├── favicon.ico
├── package-lock.json
├── package.json
├── resources
├── all-the-things.png
├── background.webp
├── climate-animation.gif
├── count.webp
├── exit-12.jpg
├── scripts.js
├── spidey-sense.png
├── styles.css
├── tim-lytle.jpg
└── whole-ass.gif
├── reveal-md.json
└── slides.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
--------------------------------------------------------------------------------
/.github/workflows/github-pages.yml:
--------------------------------------------------------------------------------
1 | # Based on https://github.com/gaerfield/reveal-md-github-pages
2 | name: Public to GitHub Pages
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | build-and-deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v2
13 | with:
14 | persist-credentials: false
15 | - name: Cache node-modules
16 | uses: actions/cache@v2
17 | env:
18 | cache-name: cache-node-modules
19 | with:
20 | path: ~/.npm
21 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
22 | - name: Install and Build
23 | run: |
24 | npm install
25 | npm run build
26 | - name: Deploy
27 | uses: JamesIves/github-pages-deploy-action@releases/v3
28 | with:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | BRANCH: gh-pages
31 | FOLDER: build
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | build
4 | node_modules
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building for the PHP Command Line Interface
2 |
3 | Executing PHP from the command line enables us to interact with our applications in new and interesting ways: from performing site maintenance to scaffolding new projects, CLI tools like [WP-CLI](http://wp-cli.org/), [Artisan](https://laravel.com/docs/5.1/artisan), and [Drush](http://www.drush.org/en/master/) make it easy to interface with our code without ever opening a browser.
4 |
5 | Attendees will be introduced to popular PHP CLI tools and their default capabilities. We'll discuss characteristics of good CLI scripts, strong use-cases for writing custom commands, then write several CLI programs across different platforms.
6 |
7 | :sparkles: **[View slides](https://stevegrunwell.github.io/building-for-php-cli)** :sparkles:
8 |
9 | This presentation also has [a companion repository, full of executable examples from this presentation](https://github.com/stevegrunwell/php-cli-examples).
10 |
11 | ### Demonstrated Tools
12 |
13 | * [Symfony Console Component](http://symfony.com/doc/current/components/console/introduction.html)
14 | * [PHP-CLI Tools](https://github.com/wp-cli/php-cli-tools)
15 | * [CLImate](https://climate.thephpleague.com/)
16 |
17 | ### Platform-specific CLI tools
18 |
19 | * [WP-CLI](https://wp-cli.org)
20 | * [Laravel Artisan](https://laravel.com/docs/master/artisan)
21 | * [Drush](https://www.drush.org)
22 | * [Joomlatools Console](https://www.joomlatools.com/developer/tools/console)
23 |
24 | ### Additional Resources
25 |
26 | * [Building PHP Daemons and Long Running Processes](https://prezi.com/pymsnzwlieqt/building-php-daemons-and-long-running-processes-tek15/) - Talk from php[tek] 2015 by [Tim Lytle](http://timlytle.net)
27 | * [Writing WP-CLI Commands That Work!](https://stevegrunwell.com/slides/wp-cli) - Sister talk focused on writing WP-CLI commands
28 | * [What are Exit Codes in Linux?](https://itsfoss.com/linux-exit-codes/) - Explanation of standard exit codes and their meanings
29 | * [Understanding Exit Codes and How to Use Them in Bash Scripts](http://bencane.com/2014/09/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/) - Article by Benjamin Cane
30 | * [Flysystem](https://flysystem.thephpleague.com) - Popular package for filesystem operations across environments
31 | * [Cropping and Resizing Animated Gifs with Gifsicle](https://stevegrunwell.com/blog/cropping-resizing-gifsicle/) - Blog post explaining how to create animated thumbnails for gifs, which relies on calling [Gifsicle](https://www.lcdf.org/gifsicle/) via [`passthru()`](https://www.php.net/manual/en/function.passthru.php)
32 |
33 | ## Presentation History
34 |
35 | * [php[tek] 2024](https://tek.phparch.com) — April 24, 2024 ([Joind.in](https://joind.in/event/phptek-2024/building-for-the-php-command-line-interface), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/phptek-2024/slides.pdf))
36 | * [Midwest PHP 2019](https://2019.midwestphp.org/) — March 8, 2019 ([Joind.in](https://joind.in/talk/b9a05), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/midwest-php/slides.pdf))
37 | * [WavePHP 2018](https://wavephp.com/) — September 20, 2018 ([Joind.in](https://joind.in/talk/6908c), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/wavephp-2018/slides.pdf))
38 | * [Southeast PHP](https://southeastphp.com/) - August 17, 2018 ([Joind.in](https://joind.in/talk/ed2e4), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/southeastphp-2018/slides.pdf))
39 | * [PHPDetroit](https://phpdetroit.io/) - July 28, 2018 ([Joind.in](https://joind.in/talk/e6d00), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/phpdetroit-2018/slides.pdf))
40 | * [php[tek] 2018](https://tek18.phparch.com/speakers/steve-grunwell/) - May 31, 2018 ([Joind.in](https://joind.in/talk/c6025), [PDF](https://github.com/stevegrunwell/building-for-php-cli/releases/download/phptek-2018/slides.pdf))
41 | * [Music City Code 2017](https://www.musiccitycode.com/) - June 3, 2017
42 | * [CodeMash 2017](http://www.codemash.org/) - January 13, 2017
43 | * [Nomad PHP (EU)](https://nomadphp.com/nomadphp-2016-12-eu/) – December 15, 2016 ([Joind.in](https://joind.in/talk/dce28))
44 | * [php[tek] 2016](https://tek16.phparch.com/speakers/#66432) – May 27, 2016 ([Joind.in](https://joind.in/talk/ce9a4))
45 | * [Columbus PHP Meetup](http://www.meetup.com/phpphp/events/229434721/) – April 13, 2016 ([Joind.in](https://joind.in/talk/e9465))
46 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/favicon.ico
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "building-for-php-cli",
3 | "private": true,
4 | "repository": {
5 | "type": "git",
6 | "url": "git@github.com:stevegrunwell/building-for-php-cli.git"
7 | },
8 | "devDependencies": {
9 | "open-cli": "^8.0.0",
10 | "reveal-md": "^6.1.0"
11 | },
12 | "scripts": {
13 | "build": "reveal-md slides.md --static build --static-dirs resources --assets-dir \"\"",
14 | "dev": "reveal-md slides.md -w",
15 | "export": "open-cli http://localhost:1948/slides.md?print-pdf",
16 | "start": "reveal-md slides.md"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/resources/all-the-things.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/all-the-things.png
--------------------------------------------------------------------------------
/resources/background.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/background.webp
--------------------------------------------------------------------------------
/resources/climate-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/climate-animation.gif
--------------------------------------------------------------------------------
/resources/count.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/count.webp
--------------------------------------------------------------------------------
/resources/exit-12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/exit-12.jpg
--------------------------------------------------------------------------------
/resources/scripts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Custom scripting for Reveal.js
3 | */
4 |
5 | /**
6 | * Construct a persistent footer.
7 | *
8 | * This is largely based on the reveal.js-titlebar package:
9 | * @link https://www.npmjs.com/package/reveal.js-titlebar
10 | */
11 | const footer = document.createElement('footer');
12 | footer.classList.add('presentation-footer');
13 | footer.innerHTML = 'phpc.social/@stevegrunwell - stevegrunwell.com/slides/php-cli';
14 | footer.setAttribute('hidden', true);
15 | document.getElementsByClassName('reveal')[0].appendChild(footer);
16 |
17 | /**
18 | * When changing to a slide with data-hide-footer, hide the presentation footer.
19 | *
20 | * @param Event event The Reveal event object.
21 | *
22 | * @return void
23 | */
24 | const toggleFooter = e => {
25 | if (e.currentSlide.dataset.hasOwnProperty('hideFooter')) {
26 | footer.setAttribute('hidden', true);
27 | } else {
28 | footer.removeAttribute('hidden');
29 | }
30 | }
31 |
32 | // Never show the footer in frame embeds
33 | if (window.self === window.top) {
34 | Reveal.on('ready', toggleFooter);
35 | Reveal.on('slidechanged', toggleFooter);
36 | }
37 |
38 | /**
39 | * Inject our custom highlight.js language (cli).
40 | *
41 | * This relies on the window.options variable defined in {@see reveal-md/lib/template/reveal.html}.
42 | */
43 | if (typeof window.options !== undefined) {
44 | window.options.highlight.beforeHighlight = (hljs) => hljs.registerLanguage('cli', (hljs) => {
45 | return {
46 | name: 'CLI',
47 | case_insensitive: true,
48 | contains: [
49 | hljs.HASH_COMMENT_MODE,
50 | hljs.QUOTE_STRING_MODE,
51 | // Bash prompt ($ or ~)
52 | {
53 | scope: 'title',
54 | match: /^\s*[\$~]\s+/,
55 | },
56 | // Boolean operators (&&, ||)
57 | {
58 | scope: 'built_in',
59 | match: /(&&|\|\|)/
60 | },
61 | // Escape characters at the end of a line
62 | {
63 | scope: 'built_in',
64 | match: /\\s*$/,
65 | },
66 | // Single pipes, redirection
67 | {
68 | scope: 'built_in',
69 | match: /[\|>]/,
70 | },
71 | // Command options
72 | {
73 | scope: 'variable',
74 | match: /\s-{1,2}[a-z0-9-_]+=?/,
75 | },
76 | ],
77 | };
78 | });
79 | }
80 |
--------------------------------------------------------------------------------
/resources/spidey-sense.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/spidey-sense.png
--------------------------------------------------------------------------------
/resources/styles.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Custom styles for Reveal.js.
3 | */
4 |
5 | /* Fonts */
6 | @import url(https://fonts.googleapis.com/css?family=Roboto+Slab:300,700;Roboto:700;Fira+Code:400);
7 |
8 | /* Override some defaults from the base theme. */
9 | :root {
10 | --r-heading-font: Roboto, 'Source Sans Pro', sans-serif;
11 | --r-body-font: 'Roboto Slab', 'Source Sans Pro', sans-serif;
12 | --r-heading1-size: 1.6em;
13 | --r-heading1-text-shadow: 0;
14 | --r-code-font: 'Fira Code', monospace;
15 | --subdued-text-color: #555;
16 | }
17 |
18 | .reveal {
19 | font-family: var(--r-body-font);
20 | background: url('./background.webp') center center repeat;
21 | }
22 |
23 | /* Special styling for the title + thank you slides. */
24 | .title-slide h1 {
25 | text-align: left;
26 | font-family: var(--r-code-font);
27 | font-weight: 100;
28 | text-transform: lowercase;
29 | word-spacing: -.25em;
30 | }
31 |
32 | .title-slide h1:before {
33 | content: '$';
34 | display: inline-block;
35 | margin-right: .5em;
36 | font-weight: bold;
37 | color: var(--r-link-color);
38 | }
39 |
40 | .title-slide h1:after {
41 | content: '\25AF';
42 | animation: blink 2s step-start infinite;
43 | font-size: calc(1em * var(--r-heading-line-height));
44 | color: var(--subdued-text-color);
45 | }
46 |
47 | @keyframes blink {
48 | 50% {
49 | opacity: 0;
50 | }
51 | }
52 |
53 | .title-slide h1,
54 | .thank-you h2 {
55 | margin-bottom: 1em;
56 | }
57 |
58 | .byline {
59 | font-weight: bold;
60 | }
61 |
62 | .byline .role {
63 | display: block;
64 | font-size: .75em;
65 | font-weight: normal;
66 | }
67 |
68 | .reveal .slides-link {
69 | display: block;
70 | margin-top: 3rem;
71 | font-size: .75em;
72 | }
73 |
74 | .slides-link a {
75 | display: block;
76 | font-weight: 300;
77 | }
78 |
79 | /* Persistent footer */
80 | .presentation-footer {
81 | --presentation-footer-color: var(--subdued-text-color);
82 |
83 | position: absolute;
84 | left: 0;
85 | right: 0;
86 | bottom: 0;
87 | z-index: 9999;
88 | padding: 0 5em .5em 1em;
89 | font-size: .5em;
90 | color: var(--presentation-footer-color);
91 | transition: .2s;
92 | }
93 |
94 | .presentation-footer[hidden] {
95 | display: none;
96 | }
97 |
98 | .presentation-footer a {
99 | display: inline-block;
100 | }
101 |
102 | @media (min-width: 600px) {
103 | .presentation-footer {
104 | font-size: .65em;
105 | }
106 | }
107 |
108 | /* Blockquotes and figures */
109 | .reveal blockquote {
110 | width: 90%;
111 | box-shadow: none;
112 | }
113 |
114 | .reveal blockquote::before {
115 | content: '\201C';
116 | position: absolute;
117 | top: .5em;
118 | left: -.3em;
119 | font-family: Georgia, serif;
120 | font-size: 2.25em;
121 | line-height: 0;
122 | color: var(--r-link-color-dark);
123 | }
124 |
125 | .reveal blockquote p:last-child {
126 | margin-bottom: 0;
127 | }
128 |
129 | .reveal cite,
130 | .reveal figure figcaption {
131 | font-size: .8em;
132 | color: var(--subdued-text-color);
133 | }
134 |
135 | .reveal cite:before {
136 | content: '—';
137 | }
138 |
139 | /* Add a bit of extra space between top-level list items. */
140 | .slides section > ol > li + li,
141 | .slides section > ul > li + li {
142 | margin-top: .5em;
143 | }
144 |
145 | /* Hide text that's hidden for the sake of presentation. */
146 | .screen-reader-text {
147 | border: 0;
148 | clip: rect(1px, 1px, 1px, 1px);
149 | clip-path: inset(50%);
150 | height: 1px;
151 | margin: -1px;
152 | overflow: hidden;
153 | padding: 0;
154 | position: absolute;
155 | width: 1px;
156 | word-wrap: normal;
157 | }
158 |
159 | /* Pull image captions closer to the image */
160 | .reveal .image-caption {
161 | margin-top: calc(-1 * var(--r-block-margin));
162 | font-size: .75em;
163 | }
164 |
165 | /* Add .hide-line-numbers to code blocks in order to hide line numbering. */
166 | .hide-line-numbers .hljs-ln-numbers {
167 | display: none;
168 | }
169 |
170 | /* Link to Tim's Building PHP Daemons and Long Running Processes talk. */
171 | .beardhawk {
172 | display: flex;
173 | flex-direction: row;
174 | gap: .5em;
175 | align-items: center;
176 | max-width: 70%;
177 | margin: 0 auto;
178 | }
179 |
180 | .beardhawk img {
181 | max-width: 3em;
182 | border-radius: 50%;
183 | }
184 |
185 | /* Allow definition lists to be centered */
186 | .reveal dl {
187 | margin-left: 0;
188 | text-align: center;
189 | }
190 |
191 | .reveal dl dd {
192 | margin-left: 0;
193 | }
194 |
195 | /* Extra space between elements in a definition list */
196 | .reveal dl + p,
197 | dd + dt {
198 | margin-top: 1em;
199 | }
200 |
201 | /* Helper to disable text-transform: uppercase on headings */
202 | .no-transform {
203 | text-transform: none;
204 | }
205 |
206 | /* Hide text that's hidden for the sake of presentation. */
207 | .screen-reader-text {
208 | border: 0;
209 | clip: rect(1px, 1px, 1px, 1px);
210 | clip-path: inset(50%);
211 | height: 1px;
212 | margin: -1px;
213 | overflow: hidden;
214 | padding: 0;
215 | position: absolute;
216 | width: 1px;
217 | word-wrap: normal;
218 | }
219 |
220 | /* Don't bold types for variables */
221 | code .typehint {
222 | font-weight: normal;
223 | font-style: italic;
224 | }
225 |
226 | /* Count von Count */
227 | .count-von-count:after {
228 | content: '';
229 | position: fixed;
230 | top: 40vh;
231 | display: block;
232 | width: 100%;
233 | height: 100vh;
234 | opacity: 0;
235 | background: url('count.webp') top center no-repeat;
236 | background-size: contain auto;
237 | transition: all .2s;
238 | }
239 |
240 | .count-von-count[data-fragment="2"]:after {
241 | opacity: 1;
242 | }
243 |
244 | /* Print styles */
245 | @media print {
246 | /* When code highlighting, don't dim anything. */
247 | .reveal .hljs.has-highlights tr {
248 | opacity: 1!important;
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/resources/tim-lytle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/tim-lytle.jpg
--------------------------------------------------------------------------------
/resources/whole-ass.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevegrunwell/building-for-php-cli/a230379ce806943f4ea3e613be467c31b5733add/resources/whole-ass.gif
--------------------------------------------------------------------------------
/reveal-md.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Building for the PHP Command Line Interface",
3 | "theme": "white",
4 | "highlightTheme": "a11y-dark",
5 | "css": [
6 | "resources/styles.css"
7 | ],
8 | "scripts": [
9 | "resources/scripts.js"
10 | ],
11 | "absoluteUrl": "https://stevegrunwell.github.io/building-for-php-cli/",
12 | "revealOptions": {
13 | "controls": true,
14 | "controlsTutorial": false,
15 | "pause": true,
16 | "pdfSeparateFragments": false,
17 | "progress": false,
18 | "slideNumber": false,
19 | "transition": "none"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/slides.md:
--------------------------------------------------------------------------------
1 |
2 | # Building for the PHP \\
Command Line Interface
3 |
4 | Steve Grunwell
5 | Staff Software Engineer, Mailchimp
6 |
7 | [@stevegrunwell@phpc.social](https://phpc.social/@stevegrunwell)
8 | [stevegrunwell.com/slides/php-cli](https://stevegrunwell.com/slides/php-cli)
9 |
10 |
11 | ---
12 |
13 | ## Why the CLI?
14 |
15 | Note:
16 |
17 | Before we talk about how, let's discuss *why* you might use PHP on the command line
18 |
19 | ----
20 |
21 | ### PHP Everywhere!
22 |
23 | * Re-use application code
24 | * Reduce language sprawl
25 | * PHP ❤️ Scripting
26 |
27 | Note:
28 |
29 | * The biggest benefit of PHP on the CLI is that we're still working in PHP:
30 | * Speaking the same language as the rest of your application
31 | * No alternate implementations, duplicative services, etc.
32 | * Keeps codebase tighter and prevents every PHP dev on your team from *also* having to write Bash or Python
33 | * PHP is a scripting language at heart
34 | * The tools we use every day (Composer, PHP_CodeSniffer, PHPUnit, PHPStan, et al) are written in PHP and interacted with solely through the command line (no GUI required!)
35 |
36 | ----
37 |
38 | ### Invoking PHP on the CLI
39 |
40 | Via the PHP binary:
41 |
42 | ```sh
43 | $ php my-command.php
44 | ```
45 |
46 |
47 | With the PHP shebang:
48 |
49 | ```sh
50 | #!/usr/bin/env php
51 | ```
52 |
53 |
54 | ```cli [1|2]
55 | $ chmod +x my-command.php
56 | $ ./my-command.php
57 | ```
58 |
59 |
60 | Note:
61 |
62 | Two ways of running PHP scripts on the command line:
63 |
64 | 1. Explicitly passing the script as an argument to the `php` binary
65 | 2. Using the PHP shebang
66 | * Probably familiar if you've done shell scripting before
67 | * Tells the shell how to interpret the script (literally "php from the user's $PATH")
68 | * Only required if you want to be able to run it without explicitly calling the PHP binary
69 |
70 | As long as the script has an executable bit in its permissions, we can run it like any other command
71 |
72 | ----
73 |
74 | ### When might I use them?
75 |
76 | * Data migrations & transformations
77 | * Maintenance scripts
78 | * Dev-only actions
79 | * Scaffolding
80 | * Other code changes
81 | * "#YOLO scripts"
82 |
83 | Note:
84 |
85 | Great places for PHP command line scripts include:
86 |
87 | * Data migrations, transformations, schema updates, table seeding, etc.
88 | * Maintenance scripts and scheduled jobs
89 | * Cron jobs, queues
90 | * Operations that are not meant to be customer facing
91 | * Scaffolding new models
92 | * Generating new migrations
93 | * YOLO scripts: scripts you're only going to run once (or a small number) of times.
94 |
95 | ---
96 |
97 | ## CLIs for your Favorite Frameworks
98 |
99 | Note:
100 |
101 | If you're working with a framework or CMS, chances are you already have the ability to talk to it via the CLI
102 |
103 | ----
104 |
105 | ### [Drush](https://www.drush.org)
106 |
107 | * "Drupal Shell"
108 | * One of the OG CLI tools for PHP CMSs
109 | * Manage themes, modules, system updates, etc.
110 |
111 | Note:
112 |
113 | Credit where credit is due, Drush ("Drupal Shell") is one of the earliest CLI tools for managing a PHP application
114 |
115 | ----
116 |
117 | ### [WP-CLI](https://wp-cli.org)
118 |
119 | * Install core, themes, plugins, etc.
120 | * Manage posts, terms, users, and more
121 | * Inspect and maintain cron, caches, and transients
122 | * Extensible for themes + plugins
123 |
124 | Note:
125 |
126 | Heavily inspired by Drush, WP-CLI lets you perform most operations on a WordPress site without touching the GUI:
127 |
128 | Before my current job, I spent five years working at a WordPress-oriented web host. We used WP-CLI for *everything*, including as part of our provisioning scripts
129 |
130 | ----
131 |
132 | ### [Laravel Artisan](https://laravel.com/docs/master/artisan)
133 |
134 | * The underlying CLI for Laravel
135 | * Built atop the Symfony Console
136 | * Scaffold
137 | * Allows packages to register new commands
138 |
139 | Note:
140 |
141 | * Artisan is the command line interface for Laravel
142 | * Built on top of Symfony Console (more in a minute)
143 | * Easily scaffold models, controllers, console commands, and more!
144 | * Third-party packages can register new commands
145 |
146 | ----
147 |
148 | ### Joomlatools Console
149 |
150 | * CLI framework for Joomla
151 | * Manage sites, extensions, databases, etc.
152 | * Includes virtual host management
153 |
154 | Note:
155 |
156 | * Joomla counterpart of WP-CLI or Drush
157 | * Similar kinds of features: managing sites, users, extensions, etc.
158 | * Kind of neat: has the "vhost" command for managing Apache + nginx virtual hosts
159 |
160 | ---
161 |
162 | ## CLI Concepts
163 |
164 | Note:
165 |
166 | While it's not any more difficult than building anything else in PHP, there are some concepts that you need to understand if you're going to build for the CLI
167 |
168 | ----
169 |
170 | ### Composability
171 |
172 | Good CLI commands should be **composable!**
173 |
174 | Note:
175 |
176 | Composability is one of the major tenents of *nix operating systems.
177 |
178 | Who can tell me what this means?
179 |
180 | ----
181 |
182 | ### Rule of Composability
183 |
184 | > Developers should write programs that can communicate easily with other programs. This rule aims to allow developers to break down projects into small, simple programs rather than overly complex monolithic programs.
185 |
186 | Eric S. Raymond, [*The Art of Unix Programming*](https://en.wikipedia.org/wiki/The_Art_of_Unix_Programming)
187 |
188 | Note:
189 |
190 | Small programs that can communicate with each other through common interfaces (data streams) and be combined to do most anything
191 |
192 | ----
193 |
194 | ### Data Streams
195 |
196 | Three default data streams:
197 |
198 | 0. STDIN - input
199 | 1. STDOUT - output
200 | 2. STDERR - errors
201 |
202 | Note:
203 |
204 | Think of a data stream as a channel that can be read from and/or written to.
205 |
206 | Generally, there are three data streams to concern yourself with:
207 |
208 | 1. STDIN represents the data coming into your command
209 | 2. STDOUT is where you're sending data out
210 | 3. STDERR is where we collect any error information
211 |
212 | Streams can be redirected (e.g. write errors to a log file, send the output of one command as the input into another)
213 |
214 | ----
215 |
216 | ### Data Streams in Practice
217 |
218 | ```cli [|2-3|4|5|6|7]
219 | # Get the number of unique IP addresses in access.log
220 | $ grep -Eo "([0-9]{1,3}[\.]){3}[0-9]{1,3}" \
221 | /var/log/nginx/access.log \
222 | | uniq \
223 | | wc -l \
224 | | xargs printf "%d unique IP addresses detected"
225 | 43282 unique IP addresses detected
226 | ```
227 |
228 |
229 | Note:
230 |
231 | Counting the number of IP addresses in access.log:
232 |
233 | 1. Use `grep` to match anything that looks like an IP, returning only that part
234 | * STDOUT would be a series of IP addresses, one per line
235 | 2. Pipe that list of addresses into `uniq` to remove duplicates
236 | * STDOUT from grep became STDIN to uniq
237 | 3. Pipe the filtered list into `wc` (word count) with the `-l` option (count the number of lines)
238 | * STDOUT becomes an integer representing the number of lines
239 | 4. Use `xargs` to append that number to printf to give a summary
240 |
241 | Five commands (grep, uniq, wc, xargs, and printf), each playing their part
242 |
243 | ----
244 |
245 | ### Exit Codes
246 |
247 | Exit codes tell us how everything went:
248 |
249 | | Code | Meaning |
250 | | --- | --- |
251 | | 0 | All good! |
252 | | 1 | Generic error |
253 | | 2 | Incorrect command/arg usage |
254 | | 3–255 | Specific errors |
255 |
256 | Note:
257 |
258 | When a command exits, we do so with an exit code.
259 |
260 | * 0 means that no errors occurred
261 | * 1–255 represent some sort of error
262 | * 1 is generally a catch-all for errors
263 | * 2 is typically meant to indicate incorrect command/arg usage
264 | * 3–255 may have special meaning; there are a few conventions in the 120s for permissions errors
265 | * You might use 3 for filesystem issues, 4 for network connectivity issues, etc.
266 |
267 | Most scripts you come across will generally use 0 or 1: did it succeed or fail (respectively)?
268 |
269 | ----
270 |
271 | ### Exit Codes & Boolean Operators
272 |
273 | ```cli [1-2|4-5|7-8|10-11]
274 | # Celebrate a non-zero exit code!
275 | $ do-something && celebrate
276 |
277 | # Hang your head in shame if something fails
278 | $ do-something || hang-head-in-shame
279 |
280 | # Put the operators together
281 | $ (do-something && celebrate) || hang-head-in-shame
282 |
283 | # Semi-colons don't care, they just separate commands
284 | $ do-something; celebrate; hang-head-in-shame
285 | ```
286 |
287 |
288 | Note:
289 |
290 | We can chain operations based on the exit code of the previous command:
291 |
292 | * Double-ampersand ("and") will proceed if the previous operation had an exit code of zero
293 | * Double pipes ("or") will proceed if we encountered a non-zero exit code
294 | * Both can be used, but use parentheses if you want the "or" to be tied to the "and" sequence
295 | * Semi-colons can chain multiple commands with no attention paid to exit codes
296 |
297 | ----
298 |
299 | ### Arguments + Options
300 |
301 | ```cli [1-3|5-8|10-12]
302 | # Arguments
303 | $ cd /var/www
304 | $ grep "Some text" file.txt
305 |
306 | # Options
307 | $ git commit -m "This is my commit message"
308 | $ ls -a -l
309 | $ ls -al
310 |
311 | # Long options
312 | $ composer outdated --format=json
313 | $ git push --force-with-lease
314 | ```
315 |
316 |
317 | Note:
318 |
319 | * Arguments: positional parameters, passed in order
320 | * Options: Can optionally have values, single dash + single letter. Can usually be combined
321 | * e.g. `ls -a -l` is the same as `ls -al`
322 | * Long options: Same as regular options, but with two dashes + multiple letters
323 | * Often easier to read or decode at a glance
324 |
325 | ----
326 |
327 | ### Conventions for Options
328 |
329 | ```plaintext
330 | OPTIONS:
331 |
332 | -h|--help Print usage instructions
333 | -q|--quiet Silence all output
334 | -v|--version Print version information
335 | --verbose Print additional output
336 | ```
337 |
338 | Note:
339 |
340 | While these aren't mandatory, there are a few common patterns you'll come across:
341 |
342 | * Many scripts will reserve `-h` and/or `--help` for displaying usage instructions
343 | * `-q` or `--quiet` is generally used to silence output
344 | * Especially useful for commands that may be run on a cron job, where you only want output if something goes wrong
345 | * `-v` has two common uses: either as a short-hand for version or verbose (print additional information)
346 |
347 | Notice that most of these options have both short and long versions!
348 |
349 | ----
350 |
351 | ### Environment Variables
352 |
353 | Set and read variables in the current environment
354 |
355 | ```cli [1-2|4-5|7-8|]
356 | # Export from shell files
357 | export CURRENT_CITY="Bowling Green"
358 |
359 | # Set directly in shell
360 | $ CURRENT_CITY="Chicago"
361 |
362 | # Set as you call a command
363 | $ CURRENT_CITY="Rosemont" some-script
364 | ```
365 |
366 |
367 | Note:
368 |
369 | There are three ways to set environment variables:
370 |
371 | 1. Export them from within a file like `.bash_profile`, which is sourced as your start your shell
372 | * Persists for all sessions
373 | 2. Explicitly set the variable in the shell
374 | * Persists for remainder of session
375 | 3. Set them as you're calling a command
376 | * Only set for the single command invocation
377 |
378 | If I set it all three of these ways, what would some-script get for the value of CURRENT_CITY? (Rosemont)
379 |
380 | ----
381 |
382 | ### Environment Variables in PHP
383 |
384 | ```php [1-2|4-5|7-8|10-11]
385 | # Get array of all environment variables
386 | getenv();
387 |
388 | # Retrieve a specific variable (false if unset)
389 | getenv('SOMEVAR');
390 |
391 | # Set an environment variable
392 | putenv('SOMEVAR=some_value');
393 |
394 | # Delete an environment variable
395 | putenv('SOMEVAR=');
396 | ```
397 |
398 |
399 | Note:
400 |
401 | There are two primary functions for working with environment variables in PHP:
402 |
403 | 1. `getenv()` reads from the environment variables
404 | 2. `putenv()` writes to the environment variables
405 |
406 | There's also the `$_ENV` superglobal, but writing to this array has no impact on the environment.
407 |
408 | ----
409 |
410 | ### The cli SAPI
411 |
412 | Additional **S**erver **API** for PHP
413 |
414 | ```php
415 | // Check the current SAPI. We can also use PHP_SAPI here.
416 | if (php_sapi_name() === 'cli') {
417 | // We're on the command line!!
418 | }
419 | ```
420 |
421 | Note:
422 |
423 | * PHP has a number of server APIs that can introduce alternate functionality; cli is one of them
424 | * Other SAPIs include apache, cgi-fcgi, fpm-fcgi, litespeed, phpdbg, etc.
425 | * We can determine what SAPI we're using with the `php_sapi_name()` function or `PHP_SAPI` constant.
426 |
427 | ----
428 |
429 | ### Special CLI globals
430 |
431 |
int $argc
array $argv
chmod()
, mkdir()
, etc.
629 | exec()
shell_exec()
system()
passthru()
escapeshellcmd()
escapeshellarg()