PHP Classes

File: components.php

Recommend this page to a friend!
  Classes of Fernando Val   Springy   components.php   Download  
File: components.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Springy
Microframework for Web application development
Author: By
Last change: Merge remote-tracking branch 'origin/master' into development
Date: 1 month ago
Size: 14,846 bytes
 

Contents

Class file image Download
#!/usr/bin/php <?php /** * Components manager. * * @copyright 2015 Fernando Val * @author Fernando Val <[email protected]> * * @version 5.0.3 * * This is script is not a Composer plugin. * * This post install/update script for Composer is not a packager version number. * It is a helper program to copy (and minify) component files from the download * destination directories to final folders in the web server accessible tree. * Than you can use your favorite package manager like Composer, NPM, Yarn, etc. * * The "components.json" file will be loaded if it exists. Then the list of * components listed inside "components" entry. * * The following attributes will be used: * * "source" - (only in components.json) The source folder where to find the files; * "target" - Destination folder to the files; * "ignore-subdirs" - If true all files will be sabed in same folder; * "minify" - "on" or "off" to minify or not the Javascript and CSS files; * "files" - The array of files or the file to be copied. Wildcards accepted. * * If there is no "files" defined for every component, their bower.json file is * used by this script to decide which files will be copied. * * NOTE: To minify CSS and JS files, is recommended the use of the Minify class * by Matthias Mullie. * https://github.com/matthiasmullie/minify */ define('DS', DIRECTORY_SEPARATOR); define('LF', "\n"); define('CS_GREEN', "\033[32m"); define('CS_RED', "\033[31m"); define('CS_RESET', "\033[0m"); define('TAB', ' '); define('LOCK_FILE', __DIR__ . DS . 'components.lock'); define('BOWER_FILE', 'bower.json'); /** * Gets the component source path. * * Also checks if the destination is defined. * * @param array $data * * @return string */ function checkComponentPath(array $data): string { if (!isset($data['source'])) { fatalError(TAB . 'Component source path undefined.'); } // Component sub directory $path = __DIR__ . DS . implode(DS, explode('/', $data['source'])); // Check component's source path if (!is_dir($path)) { echo TAB, CS_RED, 'Component\'s "', $path, '" does not exists.', CS_RESET, LF; return ''; } // Check compnent's configuration if (!isset($data['target'])) { echo TAB, CS_RED, 'Target directory not defined.', CS_RESET, LF; return ''; } return $path; } /** * Copies all files from a directory. * * @param string $path * @param string $dest * @param string $minify * * @return array */ function copyDir(string $path, string $dest, string $minify): array { $installed = []; $objects = scandir($path); foreach ($objects as $file) { if ($file == '.' || $file == '..') { continue; } $installed = array_merge( $installed, recursiveCopy($path . DS . $file, $dest . DS . $file, $minify) ); } return $installed; } /** * Copy a file. * * @param string $path * @param string $dest * @param string $minify * * @return void */ function copyFile(string $path, string $dest, string $minify): void { // The source is a file $dir = dirname($dest); grantDestination($dir); // Copy only if source is new or newer if (is_file($dest) && filemtime($path) < filemtime($dest)) { return; } elseif (!realCopy($path, $dest, $minify)) { echo TAB, CS_RED, '[ERROR] Copying (', $path, ') to (', $dest, ')', CS_RESET, LF; } } /** * Removes an empty directory. * * @param array $dir * * @return void */ function delDir(array $dir): void { $type = $dir['type'] ?? ''; $path = $dir['path'] ?? ''; if ($type !== 'd' || !$path) { return; } elseif (is_dir($path) && count(getDir($path)) === 0 && !rmdir($path)) { echo TAB, CS_RED, 'Fail to delete "', $path, '" file.', CS_RESET, LF; } } /** * Deletes a file. * * @param array $file * * @return void */ function delFile(array $file): void { $type = $file['type'] ?? ''; $path = $file['path'] ?? ''; if ($type !== 'f' || !$path) { return; } elseif (is_file($path) && !unlink($path)) { echo TAB, CS_RED, 'Fail to delete "', $path, '" file.', CS_RESET, LF; } $dir = dirname($path); $files = glob($dir . DS . '{.[!.],}*', GLOB_BRACE); if (is_array($files) && !count($files)) { delDir([ 'type' => 'd', 'path' => $dir, ]); } } /** * Verifies all installed components that is no more listed inside Json. * * @param array $components * * @return void */ function delRemovedComponents(array $components): void { $installed = loadLockFile(); // Verify if any component was removed foreach (array_reverse($installed) as $name => $files) { if (isset($components[$name])) { continue; } echo ' - Deleting ', CS_GREEN, $name, CS_RESET, ' files', LF; foreach (array_reverse($files) as $file) { delDir($file); delFile($file); } } } /** * Terminates the program with an error message. * * @param string $error * * @return void * * @SuppressWarnings(PHPMD.ExitExpression) */ function fatalError(string $error): void { echo CS_RED, $error, CS_RESET, LF; exit(1); } /** * Gets the list of files from Bower Json. * * @param string $path * * @return array */ function getBowerMain(string $path): array { if (!file_exists($path . DS . BOWER_FILE)) { return ['*']; } $bower = loadJson($path . DS . BOWER_FILE); if (!isset($bower['main'])) { echo TAB, CS_RED, 'Main section does not exists in "' . $path . DS . BOWER_FILE . '" file.', CS_RESET, LF; return []; } return is_array($bower['main']) ? $bower['main'] : [$bower['main']]; } /** * Gets the list of files of the component. * * @param array $data * @param string $path * * @return array */ function getComponentFiles(array $data, string $path): array { if (isset($data['files'])) { return is_array($data['files']) ? $data['files'] : [$data['files']]; } return getBowerMain($path); } /** * Returns an array with directory content without . and .. special dirs. * * @param string $path * * @return array */ function getDir(string $path): array { return array_filter( scandir($path), fn ($file) => $file !== '.' && $file !== '..' ); } /** * Gets the destination folder for the component. * * Creates the folder if does not exists. * * @param string $component * @param array $data * * @return string */ function getDestinantion(string $component, array $data) { if (!is_string($data['target'])) { fatalError(TAB . 'No destination defined for "' . $component . '" component.'); } $destination = __DIR__ . DS . implode(DS, explode('/', $data['target'])); if (!is_dir($destination) && !mkdir($destination, 0775, true)) { fatalError(TAB . 'Can\'t create "' . $destination . '" directory.'); } return $destination; } /** * Grants the existance of the directory. * * @param string $dir * * @return void */ function grantDestination(string $dir): void { if (is_dir($dir)) { return; } elseif (!mkdir($dir, 0775, true)) { echo TAB, CS_RED, 'Can\'t create "', $dir, '" directory.', CS_RESET, LF; } } /** * Loads the components.json file and returns an array with components list. * * @return array */ function loadComponentsJson(): array { $jsonpath = __DIR__ . DS . 'components.json'; if (!file_exists($jsonpath)) { return []; } $json = loadJson($jsonpath); if (!is_array($json['components'] ?? null)) { fatalError('Syntax error in components.json'); } $components = []; foreach ($json['components'] as $name => $data) { $components[$name] = $data; } return $components; } /** * Parses the Json file. * * @param string $json * * @return array */ function loadJson(string $filepath): array { $jsonstr = file_get_contents($filepath); if (!$jsonstr) { fatalError('Can\'t open ' . $filepath . ' file.'); } $parsed = json_decode($jsonstr, true); $error = json_last_error(); if ($error === JSON_ERROR_NONE) { return $parsed; } $jsonErrors = [ JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', JSON_ERROR_SYNTAX => 'Syntax error', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded', JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded', JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given', ]; fatalError($jsonErrors[$error] ?? 'Unknown error occurred'); } /** * Loads the components.lock file. * * @return array */ function loadLockFile(): array { if (!file_exists(LOCK_FILE)) { return []; } $lock = file_get_contents(LOCK_FILE); if (!$lock) { fatalError('Can\'t open ' . LOCK_FILE . ' file.'); } return unserialize($lock); } /** * Minify a file using Mattias Mullie's component. * * @param string $buffer * @param string $ext * * @return string */ function matthiasMullie(string $buffer, string $ext): string { if ($ext !== 'css' && $ext !== 'js') { echo TAB, CS_RED, '[WARNING] Invalid minify method: ', $ext, CS_RESET, LF; return $buffer; } $minifier = 'css' ? new MatthiasMullie\Minify\CSS($buffer) : new MatthiasMullie\Minify\JS($buffer); return $minifier->minify(); } /** * Minify the file if turned on. * * @param string $buffer * @param string $minify * * @return string */ function minifyFile(string $buffer, string $minify): string { if ($minify == 'off') { return $buffer; } if (class_exists('MatthiasMullie\Minify\Minify')) { return matthiasMullie($buffer, $minify); } // Matthias Mullie's Minify class not found. I Will try by myself but this is not the best way. switch ($minify) { case 'css': $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer); $buffer = str_replace(["\r\n", "\r", "\n", "\t", ' ', TAB, ' '], '', $buffer); $buffer = preg_replace(['(( )+{)', '({( )+)'], '{', $buffer); $buffer = preg_replace(['(( )+})', '(}( )+)', '(;( )*})'], '}', $buffer); $buffer = preg_replace(['(;( )+)', '(( )+;)'], ';', $buffer); break; case 'js': $buffer = preg_replace("/((?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:\/\/.*))/", '', $buffer); $buffer = str_replace(["\r\n", "\r", "\t", "\n", ' ', TAB, ' '], '', $buffer); $buffer = preg_replace(['(( )+\))', '(\)( )+)'], ')', $buffer); break; default: echo TAB, CS_RED, '[WARNING] Invalid minify method: ', $minify, CS_RESET, LF; } return $buffer; } /** * Copy file minifyint if necessary. * * @param string $source * @param string $destiny * @param string $minify * * @return bool */ function realCopy(string $source, string $destiny, string $minify = 'auto') { if ($minify == 'auto' || $minify == 'on') { $minify = (substr($source, -4) == '.css' ? 'css' : (substr($source, -3) == '.js' ? 'js' : 'off')); } $buffer = file_get_contents($source); if ($buffer == false) { echo TAB, CS_RED, '[ERROR] Failed to open ', $source, CS_RESET, LF; return false; } $buffer = minifyFile($buffer, $minify); $return = file_put_contents($destiny, minifyFile($buffer, $minify)); if ($return !== false) { chmod($destiny, 0664); } return $return; } /** * Copy files or directory recursively. * * @param string $path * @param string $dest * @param string $minify * @param string $component * * @return array */ function recursiveCopy(string $path, string $dest, string $minify): array { $installed = []; if (is_dir($path)) { // The source is a directory return array_merge( $installed, copyDir($path, $dest, $minify) ); } elseif (is_file($path)) { copyFile($path, $dest, $minify); $installed[] = [ 'path' => $dest, 'type' => 'f', ]; return $installed; } /* * Oh! Is a wildcard path. */ $dest = dirname($dest); foreach (glob($path) as $filename) { $installed = array_merge( $installed, recursiveCopy($filename, $dest . DS . basename($filename), $minify) ); } return $installed; } /** * Adds Composer autoload if exists. * * @return void */ function requireComposerAutoload(): void { $composer = loadJson(__DIR__ . DS . 'composer.json'); $vendor = isset($composer['config']) && isset($composer['config']['vendor-dir']) ? $composer['config']['vendor-dir'] : 'vendor'; if (file_exists($vendor . DS . 'autoload.php')) { require $vendor . DS . 'autoload.php'; } } /** @var array components list */ $components = loadComponentsJson(); /** @var array installed components list */ $installed = []; echo CS_GREEN, 'Starting the installation of the extra components', CS_RESET, LF; requireComposerAutoload(); delRemovedComponents($components); // Process every component foreach ($components as $name => $data) { echo ' - Processing ', CS_GREEN, $name, CS_RESET, ' files', LF; $installed[$name] = []; $path = checkComponentPath($data); if (!$path) { continue; } // Component properties $files = getComponentFiles($data, $path); $noSubdirs = $data['ignore-subdirs'] ?? false; $minify = $data['minify'] ?? 'off'; $destination = getDestinantion($name, $data); $installed[$name][] = [ 'path' => $destination, 'type' => 'd', ]; foreach ($files as $file) { $file = implode(DS, explode('/', $file)); $dstFile = $file; if ($noSubdirs) { $dstFile = explode('/', $file); $dstFile = array_pop($dstFile); } $installed[$name] = array_merge( $installed[$name], recursiveCopy($path . DS . $file, $destination . DS . $dstFile, $minify) ); } } // Write the lock file echo CS_GREEN, 'Writing lock file', CS_RESET, LF; if (!file_put_contents(LOCK_FILE, serialize($installed))) { fatalError('Can\'t write ' . LOCK_FILE . ' file.'); }