php, simulate include? Cache system - php

I'm trying to create a small template system and have a function that loops over an array of items.
Currently I'm using the output buffering functions and include so i can load up the template file while it has scope to the class.
function loadTemplate($name, $vars) {
$buf = '';
$path = $name . '.html';
if (file_exists($path)) {
$this->vars = $vars;
ob_start();
include($path);
$buf = ob_get_clean();
}
return $buf;
}
I was just wondering if I could store the initial template in an array then have it run (As if it was included) while keeping scope, like.
function loadTemplate($name, $vars) {
$buf = $template = '';
if (isset($this->cache[$name]))
$template = $this->cache[$name];
else {
$path = $name . '.html';
$template = file_get_contents($path);
$this->cache[$name] = $template;
}
//Exec template here with scope.
}
Or am i just being pedantic and trying to micro optimize :)

If i were you and had complex operations in the template files I would save them to filesystem. I've modified your function, I think you'll understand what happens there:
<?php
function template($name, $vars = array())
{
$cache = 'cache/'; // Path to cache folder, must be writeable
$expire = 3600 * 3; // Cache lifetime, 3 hours
$path = $name . '.html';
$cache_file = $cache . sha1($path) . '.txt'; // Generate cache file path and hash-name
// If cache file exists and it hasn't expired yet we must get cached data
if (file_exists($cache_file) && filemtime($cache_file) > (time() - $expire))
{
return unserialize(file_get_contents($cache_file));
}
// Return NULL if template file doesn't exist
if (!file_exists($path))
{
return null;
}
$this->vars = $vars;
ob_start();
include_once $path;
$output = ob_get_clean();
// Save output to the cache file
file_put_contents($cache_file, serialize($output));
return $output;
}
?>
P.S. Haven't tested the function.

That's most useless cache you can implement.
You'd better think of HTTP conditional get implementation which will make no need to call temlpate at all. And then go for opcode cache which will cache your includes automatically.
But at firs you have to profile your app/templater to see if you need any cache at all

Just keep including it. The only alternative would be to read the contents then eval them, and that's going to be worse. The overhead of the second include should be significantly less since the page is already parsed into opcode...

Will be looking into CakePHP as per NullUserException's comment :)

I don't think that it makes much difference if you include an template once again, as you said yourself... it would be micro optimizing.
But, what you could do is to save the already included templates source to an array and use the template name as the key for the array.
When you run your loadTemplate function, you can just do a array_key_exists to see if it is included already.
But if I may, I would recommend the smarty template engine. I used it in my projects and find it just perfect. I have adapted it a bit to run smoother with my code, but now it really is perfect for me.

Related

Save file to memory and later write back

I wish to read a file using PHP, and later write it to a directory which doesn't exist at the time of reading the file. I can't create the directory first as described below. I do not wish to save it in a temporary directory to prevent possible overwrites. Am I able to read the file, save it in memory, and later write the file?
WHY I WISH TO DO THIS: I have the following method which empties a directory. I now have a need to do so but keep one file in the root of the emptied directory. I recognize I could modify this method to do so, but I rarely need to do so, and may wish another approach. Instead, before calling this method, I would like to copy the file in question, empty the directory, and then put it back.
/**
* Empty directory. Include subdirectories if $deep is true
*/
public static function emptyDir($dirname,$deep=false)
{
$dirname=(substr($dirname, -1)=='/')?$dirname:$dirname.'/';
if(!is_dir($dirname)){return false;}
// Loop through the folder
$dir = dir($dirname);
while (false !== $entry = $dir->read())
{
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
elseif(is_file($dirname.$entry)) {
unlink($dirname.$entry);
}
elseif($deep && is_dir($dirname.$entry)){
self::deltree($dirname.$entry);
}
}
// Clean up
$dir->close();
return true;
}
Provided this is all done withing the same request, then yes you can.
Just save the file contents to a variable, then write it back again:
$temp = file_get_contents('path/to/file.ext');
className::emptyDir($dir);
file_put_contents('path/to/file.ext', $temp);
Yes, it could be done. Just add a property to your class. So in your class property, there will be the content of the file, while the object is exists, and it did set. It could be a class variable (static) also, so you do not need to instantiate if you do not want.
class anything {
var $fileContent = '';
public static function emptyDir($dirname,$deep=false) {
//....
}
public function setFileContent($fileOrUrlToRead) {
$this->fileContent = file_get_contents($fileOrUrlToRead);
}
public function saveFile($fileName) {
file_put_contents($fileName, $this->fileContent);
}
}
$anything = new anything();
$anything->setFileContent('url_or_path_of_file_to_get');
anything::emptyDir('./media/files/');
$anything->saveFile('./media/files/something.txt');
You can use the session to save the needed information.

Creating a folder when I run file_put_contents()

I have uploaded a lot of images from the website, and need to organize files in a better way.
Therefore, I decide to create a folder by months.
$month = date('Yd')
file_put_contents("upload/promotions/".$month."/".$image, $contents_data);
after I tried this one, I get error result.
Message: file_put_contents(upload/promotions/201211/ang232.png): failed to open stream: No such file or directory
If I tried to put only file in exist folder, it worked. However, it failed to create a new folder.
Is there a way to solve this problem?
file_put_contents() does not create the directory structure. Only the file.
You will need to add logic to your script to test if the month directory exists. If not, use mkdir() first.
if (!is_dir('upload/promotions/' . $month)) {
// dir doesn't exist, make it
mkdir('upload/promotions/' . $month);
}
file_put_contents('upload/promotions/' . $month . '/' . $image, $contents_data);
Update: mkdir() accepts a third parameter of $recursive which will create any missing directory structure. Might be useful if you need to create multiple directories.
Example with recursive and directory permissions set to 777:
mkdir('upload/promotions/' . $month, 0777, true);
modification of above answer to make it a bit more generic, (automatically detects and creates folder from arbitrary filename on system slashes)
ps previous answer is awesome
/**
* create file with content, and create folder structure if doesn't exist
* #param String $filepath
* #param String $message
*/
function forceFilePutContents ($filepath, $message){
try {
$isInFolder = preg_match("/^(.*)\/([^\/]+)$/", $filepath, $filepathMatches);
if($isInFolder) {
$folderName = $filepathMatches[1];
$fileName = $filepathMatches[2];
if (!is_dir($folderName)) {
mkdir($folderName, 0777, true);
}
}
file_put_contents($filepath, $message);
} catch (Exception $e) {
echo "ERR: error writing '$message' to '$filepath', ". $e->getMessage();
}
}
i have Been Working on the laravel Project With the Crud Generator and this Method is not Working
#aqm so i have created my own function
PHP Way
function forceFilePutContents (string $fullPathWithFileName, string $fileContents)
{
$exploded = explode(DIRECTORY_SEPARATOR,$fullPathWithFileName);
array_pop($exploded);
$directoryPathOnly = implode(DIRECTORY_SEPARATOR,$exploded);
if (!file_exists($directoryPathOnly))
{
mkdir($directoryPathOnly,0775,true);
}
file_put_contents($fullPathWithFileName, $fileContents);
}
LARAVEL WAY
Don't forget to add at top of the file
use Illuminate\Support\Facades\File;
function forceFilePutContents (string $fullPathWithFileName, string $fileContents)
{
$exploded = explode(DIRECTORY_SEPARATOR,$fullPathWithFileName);
array_pop($exploded);
$directoryPathOnly = implode(DIRECTORY_SEPARATOR,$exploded);
if (!File::exists($directoryPathOnly))
{
File::makeDirectory($directoryPathOnly,0775,true,false);
}
File::put($fullPathWithFileName,$fileContents);
}
I created an simpler answer from #Manojkiran.A and #Savageman. This function can be used as drop-in replacement for file_put_contents. It doesn't support context parameter but I think should be enough for most cases. I hope this helps some people. Happy coding! :)
function force_file_put_contents (string $pathWithFileName, mixed $data, int $flags = 0) {
$dirPathOnly = dirname($pathWithFileName);
if (!file_exists($dirPathOnly)) {
mkdir($dirPathOnly, 0775, true); // folder permission 0775
}
file_put_contents($pathWithFileName, $data, $flags);
}
Easy Laravel solution:
use Illuminate\Support\Facades\File;
// If the directory does not exist, it will be create
// Works recursively, with unlimited number of subdirectories
File::ensureDirectoryExists('my/super/directory');
// Write file content
File::put('my/super/directory/my-file.txt', 'this is file content');
I wrote a function you might like. It is called forceDir(). It basicaly checks whether the dir you want exists. If so, it does nothing. If not, it will create the directory. A reason to use this function, instead of just mkdir, is that this function can create nexted folders as well.. For example ('upload/promotions/januari/firstHalfOfTheMonth'). Just add the path to the desired dir_path.
function forceDir($dir){
if(!is_dir($dir)){
$dir_p = explode('/',$dir);
for($a = 1 ; $a <= count($dir_p) ; $a++){
#mkdir(implode('/',array_slice($dir_p,0,$a)));
}
}
}

Is it possible to track a variable back to its extract() function?

I am working with a Drupal theme, and I see a lot of variables which look like were created with extract(). Is it possible to track back, and see where that array is?
I take you are referring to the variables passed to a template file, which effectively are extracted from an array.
The code that does that in Drupal 7 is in theme_render_template().
function theme_render_template($template_file, $variables) {
extract($variables, EXTR_SKIP); // Extract the variables to a local namespace
ob_start(); // Start output buffering
include DRUPAL_ROOT . '/' . $template_file; // Include the template file
return ob_get_clean(); // End buffering and return its contents
}
The function is called from theme(), which executes the following code.
// Render the output using the template file.
$template_file = $info['template'] . $extension;
if (isset($info['path'])) {
$template_file = $info['path'] . '/' . $template_file;
}
$output = $render_function($template_file, $variables);
$render_function by default is set to 'theme_render_template', but its value is set with the following code (in theme()).
// The theme engine may use a different extension and a different renderer.
global $theme_engine;
if (isset($theme_engine)) {
if ($info['type'] != 'module') {
if (function_exists($theme_engine . '_render_template')) {
$render_function = $theme_engine . '_render_template';
}
$extension_function = $theme_engine . '_extension';
if (function_exists($extension_function)) {
$extension = $extension_function();
}
}
}
Just echo the $GLOBALS variable and you might find where it came from if the array was not unset.
Im not familiar with Drupal so this is just a suggestion, but if drupal has a templating structure or if an array is passed from a controller or such then possible that extract is used,
You could use get_defined_vars within your view to get all vars and its possible that there is an array there that you can cross reference with variables you know of that are in the same array or such.
<?php
$vars = get_defined_vars();
print_r($vars);
//or maybe
print_r($this);
?>

File private variables in PHP

Is it possible to define private variables in a PHP script so these variables are only visible in this single PHP script and nowhere else? I want to have an include file which does something without polluting the global namespace. It must work with PHP 5.2 so PHP namespaces are not an option. And no OOP is used here so I'm not searching for private class members. I'm searching for "somewhat-global" variables which are global in the current script but nowhere else.
In C I could do it with the static keyword but is there something similar in PHP?
Here is a short example of a "common.php" script:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
When I include this file in some script then the $dir variable is visible in all other scripts as well and I don't want that. So how can I prevent this?
There are a few things you could do to keep $dir out of subsequent files
Example 1
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
This is the most obvious.
Example 2
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// work with $dir
unset($dir);
Just unset the variable after defining it and using it. Note this will unset any variable named $dir used prior to including this script.
Example 3
define('DIR_THIS', dirname(__FILE__));
set_include_path(DIR_THIS . PATH_SEPARATOR . get_include_path());
It is less likely I suppose to redefine a global constant like this.
Example 4
function my_set_include_path {
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
$my_other_var = 'is trapped within this function';
}
my_set_include_path();
You can define as many variables within that function and not affect the global namespace.
Conclusion
The first method is the easiest way to solve this problem, however because you want to use $dir again, it may not be ideal. The last example will at least keep that $dir (and any others defined in that function) out of the global namespace.
The only way you're going to accomplish anything close to what you want is to wrap everything in that included file in a function, and call it. If the file needs to execute itself you could still do
<?php
run_myfile()
function run_myfile() {
...
}
?>
There is no generic way to make a variable scoped to only a file outside of namespaces, classes, or functions.
Well, I'm probably getting flailed for this, but you if you are totally desperate you could use a Registry for that. I've whipped up a small one that does without classes (since I assume from And no OOP is used here so I'm not searching for private class members. means you don't want to do it with OOP at all)
function &registry_get_instance()
{
static $data = array();
return $data;
}
The static $data variable inside is persisted inside the function scope, so you can call the function wherever you like and always get the same contents. The crucial point is returning by reference, e.g.
$registry = &registry_get_instance(); // get $data array by reference
$registry['foo'] = 'bar'; // set something to $data
unset($registry); // delete global reference to $data
print_r(&registry_get_instance()); // show $data
Obviously you'd still have $registry as a variable in the global scope when calling this method from the global scope. So, you could add some more functions to make the Registry more convenient to use, e.g. for setting data to the Registry:
function registry_set($key, $value)
{
$registry = &registry_get_instance();
$registry[$key] = $value;
}
and for getting it out again:
function registry_get($key)
{
$registry = &registry_get_instance();
if(array_key_exists($key, $registry)) {
return $registry[$key];
} else {
trigger_error(sprintf(
'Undefined Index: %s', htmlentities($key)
), E_USER_NOTICE);
}
}
and for checking if a key exists:
function registry_isset($key)
{
$registry = &registry_get_instance();
return array_key_exists($key, $registry);
}
which you could then use like:
registry_set('foo', 'bar'); // setting something to the registry
var_dump( registry_isset('foo') ); // check foo is in the registry now
echo registry_get('foo'); // prints 'bar'
echo registry_get('punt'); // raises Notice
You could populate the Registry from an include file with an additional method like this:
function registry_load_file($file)
{
if(!is_readable(realpath($file))) {
return trigger_error(sprintf(
'File is not readable: %s', htmlentities($file)
), E_USER_WARNING);
}
$config = include $file;
if(!is_array($config)) {
return trigger_error(sprintf(
'Expected file %s to return an array', htmlentities($file))
, E_USER_WARNING);
}
$registry = &registry_get_instance();
$registry += $config;
}
with the include file having to return an array:
// config.php
return array(
'setting1' => 'something'
);
and then you can do
registry_load_from_file('config.php'); // add the contents of config to registry
print_r(registry_get_instance()); // show content of registry
Of course, this is now six functions in the global scope just for not having a global variable. Don't know if it's worth it, especially since I consider static in functions and all that reference stuff doubtful practice.
Take it as a proof of concept :)
Why not just put everything in a static class? Then you only have a single "variable" that could possibly conflict with the global namespace.
class MyClass {
public static $myvar = 1;
public static $myvar2 = "xyz";
public static function myfunction() {
self::$myvar++;
self::$myvar2 = "abc";
}
}
// References to class items, if needed
MyClass::myfunction();
MyClass::$myvar += 3;
If the problem you are trying to is just:
$dir = dirname(__FILE__);
set_include_path($dir . PATH_SEPARATOR . get_include_path());
// Do more stuff with the $dir variable
Then the solution would be to change the include path relative to '.' in your ini settings. E.g. change:
include_path=includes:/usr/local/php
to
include_path=./includes:/usr/local/php
Note that a script does not come into scope except where you explicitly include/require it (both the _once check applies globally) however I would recommend strongly against calling include/require from within a function - its much more transparent having the includes/requires at the top of the script.
I think that the problem you are trying to solve is based on a false premise and you should look for another way of fixing it. If you want the code in an include file to behave differently depending on what includes it, then really you should seperate it out into 2 seperate files - or maybe even 3 - 2 for the different behaviours and 1 for the common.
C.

How to include() all PHP files from a directory?

In PHP can I include a directory of scripts?
i.e. Instead of:
include('classes/Class1.php');
include('classes/Class2.php');
is there something like:
include('classes/*');
Couldn't seem to find a good way of including a collection of about 10 sub-classes for a particular class.
foreach (glob("classes/*.php") as $filename)
{
include $filename;
}
Here is the way I include lots of classes from several folders in PHP 5. This will only work if you have classes though.
/*Directories that contain classes*/
$classesDir = array (
ROOT_DIR.'classes/',
ROOT_DIR.'firephp/',
ROOT_DIR.'includes/'
);
function __autoload($class_name) {
global $classesDir;
foreach ($classesDir as $directory) {
if (file_exists($directory . $class_name . '.php')) {
require_once ($directory . $class_name . '.php');
return;
}
}
}
I realize this is an older post BUT... DON'T INCLUDE YOUR CLASSES... instead use __autoload
function __autoload($class_name) {
require_once('classes/'.$class_name.'.class.php');
}
$user = new User();
Then whenever you call a new class that hasn't been included yet php will auto fire __autoload and include it for you
this is just a modification of Karsten's code
function include_all_php($folder){
foreach (glob("{$folder}/*.php") as $filename)
{
include $filename;
}
}
include_all_php("my_classes");
How to do this in 2017:
spl_autoload_register( function ($class_name) {
$CLASSES_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR; // or whatever your directory is
$file = $CLASSES_DIR . $class_name . '.php';
if( file_exists( $file ) ) include $file; // only include if file exists, otherwise we might enter some conflicts with other pieces of code which are also using the spl_autoload_register function
} );
Recommended by PHP documentation here: Autoloading classes
You can use set_include_path:
set_include_path('classes/');
http://php.net/manual/en/function.set-include-path.php
If there are NO dependencies between files... here is a recursive function to include_once ALL php files in ALL subdirs:
$paths = [];
function include_recursive( $path, $debug=false){
foreach( glob( "$path/*") as $filename){
if( strpos( $filename, '.php') !== FALSE){
# php files:
include_once $filename;
if( $debug) echo "<!-- included: $filename -->\n";
} elseif( is_dir($filename)) { # dirs
$paths[] = $filename;
}
}
# Time to process the dirs:
for( $i=count($paths)-1; $i>=0; $i--){
$path = $paths[$i];
unset( $paths[$i]);
include_recursive( $path, $debug);
}
}
include_recursive( "tree_to_include");
# or... to view debug in page source:
include_recursive( "tree_to_include", 'debug');
<?php
//Loading all php files into of functions/ folder
$folder = "./functions/";
$files = glob($folder."*.php"); // return array files
foreach($files as $phpFile){   
require_once("$phpFile");
}
If you want include all in a directory AND its subdirectories:
$dir = "classes/";
$dh = opendir($dir);
$dir_list = array($dir);
while (false !== ($filename = readdir($dh))) {
if($filename!="."&&$filename!=".."&&is_dir($dir.$filename))
array_push($dir_list, $dir.$filename."/");
}
foreach ($dir_list as $dir) {
foreach (glob($dir."*.php") as $filename)
require_once $filename;
}
Don't forget that it will use alphabetic order to include your files.
If your looking to include a bunch of classes without having to define each class at once you can use:
$directories = array(
'system/',
'system/db/',
'system/common/'
);
foreach ($directories as $directory) {
foreach(glob($directory . "*.php") as $class) {
include_once $class;
}
}
This way you can just define the class on the php file containing the class and not a whole list of $thisclass = new thisclass();
As for how well it handles all the files? I'm not sure there might be a slight speed decrease with this.
I suggest you use a readdir() function and then loop and include the files (see the 1st example on that page).
Try using a library for that purpose.
That is a simple implementation for the same idea I have build.
It include the specified directory and subdirectories files.
IncludeAll
Install it via terminal [cmd]
composer install php_modules/include-all
Or set it as a dependency in the package.json file
{
"require": {
"php_modules/include-all": "^1.0.5"
}
}
Using
$includeAll = requires ('include-all');
$includeAll->includeAll ('./path/to/directory');
This is a late answer which refers to PHP > 7.2 up to PHP 8.
The OP does not ask about classes in the title, but from his wording we can read that he wants to include classes. (btw. this method also works with namespaces).
By using require_once you kill three mosquitoes with one towel.
first, you get a meaningful punch in the form of an error message in your logfile if the file doesn't exist. which is very useful when debugging.( include would just generate a warning that might not be that detailed)
you include only files that contain classes
you avoid loading a class twice
spl_autoload_register( function ($class_name) {
require_once '/var/www/homepage/classes/' . $class_name . '.class.php';
} );
this will work with classes
new class_name;
or namespaces. e.g. ...
use homepage\classes\class_name;
Answer ported over from another question. Includes additional info on the limits of using a helper function, along with a helper function for loading all variables in included files.
There is no native "include all from folder" in PHP. However, it's not very complicated to accomplish. You can glob the path for .php files and include the files in a loop:
foreach (glob("test/*.php") as $file) {
include_once $file;
}
In this answer, I'm using include_once for including the files. Please feel free to change that to include, require or require_once as necessary.
You can turn this into a simple helper function:
function import_folder(string $dirname) {
foreach (glob("{$dirname}/*.php") as $file) {
include_once $file;
}
}
If your files define classes, functions, constants etc. that are scope-independent, this will work as expected. However, if your file has variables, you have to "collect" them with get_defined_vars() and return them from the function. Otherwise, they'd be "lost" into the function scope, instead of being imported into the original scope.
If you need to import variables from files included within a function, you can:
function load_vars(string $path): array {
include_once $path;
unset($path);
return get_defined_vars();
}
This function, which you can combine with the import_folder, will return an array with all variables defined in the included file. If you want to load variables from multiple files, you can:
function import_folder_vars(string $dirname): array {
$vars = [];
foreach (glob("{$dirname}/*.php") as $file) {
// If you want to combine them into one array:
$vars = array_merge($vars, load_vars($file));
// If you want to group them by file:
// $vars[$file] = load_vars($file);
}
return $vars;
}
The above would, depending on your preference (comment/uncomment as necessary), return all variables defined in included files as a single array, or grouped by the files they were defined in.
On a final note: If all you need to do is load classes, it's a good idea to instead have them autoloaded on demand using spl_autoload_register. Using an autoloader assumes that you have structured your filesystem and named your classes and namespaces consistently.
Do no write a function() to include files in a directory. You may lose the variable scopes, and may have to use "global". Just loop on the files.
Also, you may run into difficulties when an included file has a class name that will extend to the other class defined in the other file - which is not yet included. So, be careful.

Categories