This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have this PHP code, but I have only written one recursive function before. I want to begin in a particular subdirectory and check for the presence of a file. If it doesn't exist in the directory I want to check the parent folder for the file and then recursively go on until I either don't find the file and return "" or find the file and return its contents. Here is my function:
/**
* Recursive function to get sidebar from path and look up tree until we either
* find it or return blank.
*/
class MyClass{
public function findSidebarFromPath($path){
if(file_exists($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html")){
return file_get_contents($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html");
} else {
if(count($path) > 0){
# Pop element off end of array.
$discard = array_pop($path);
$this->findSidebarFromPath($path);
} else {
return "";
}
}
}
}
So when I call it, I do this:
$myclass = new MyClass();
$myclass->findSidebarFromPath(array("segment1", "segment2", "segment3"));
Let's say the file I am trying to find is in a directory called "segment2". The function never finds it. The array_pop function doesn't pop off the element off the end of the array and then call the findSidebarFromPath function.
If you write this as a standalone function, it will probably be more useful to you in other areas. After we understand how it works, we'll show you how you can add a public function to your class that can utilize it.
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
// create auxiliary function that takes args in format we want
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
// resolve the complete filename
$file = "{$cwd}/{$filename}";
// if the file exists, return the contents
if (file_exists($file)) return file_get_contents($file);
// if the cwd has already reached the root, do not go up a directory; return false instead
if ($cwd === $root) return false;
// otherwise check the above directory
return $findFileAux(dirname($cwd), $filename);
};
// starting checking for the file at the deepest segment
return $findFileAux("{$root}/{$path}", $filename);
}
Check the example output on ideone.
So here's how to use it
findFile($_SERVER["DOCUMENT_ROOT"], "foo/bar/qux", "sidebar.html");
Here's how you would integrate it with your class. Notice that this public function has the same API as in your original code
class MyClass {
/**
* #param $path array - path segments to search in
* #return mixed - string file contents or false if sidebar is not found
*/
public function findSidebarFromPath($path) {
// Here we call upon the function we wrote above
// making sure to `join` the path segments into a string
return findFile($_SERVER["DOCUMENT_ROOT"], join("/", $path), "sidebar.html";
}
}
Additional explanation
If $_SERVER["DOCUMENT_ROOT"] is /var/www...
Check /var/www/foo/bar/qux/sidebar.html, return if exists
Check /var/www/foo/bar/sidebar.html, return if exists
Check /var/www/foo/sidebar.html, return if exists
Check /var/www/sidebar.html, return if exists
Because we got to the root (/var/www) no further searches will happen
return false if sidebar.html did not exist in any of the above
Here's the same function with the explanatory comments removed
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
$file = "{$cwd}/{$filename}";
if (file_exists($file)) return file_get_contents($file);
if ($cwd === $root) return false;
return $findFileAux(dirname($cwd), $filename);
};
return $findFileAux("{$root}/{$path}", $filename);
}
You might also want to consider using DIRECTORY_SEPARATOR instead of the hard-coded "/" so that this code could be used reliably on a variety of platforms.
Related
I'm stumped on a XenForo 1.5.7 / php7 issue. I've read that tempnam() was changed as of php7 (based on temp dir permissions), but I've chmod'd the directory as that link states, still to no avail.
I printed out $newTempFile which returns /var/www/forum/internal_data/temp/xfJ9FLyG (looks correct). It's the next line, the $image variable, that does not get set, and then throws the error in the if() below.
$newTempFile = tempnam(XenForo_Helper_File::getTempDir(), 'xf');
$image = XenForo_Image_Abstract::createFromFile($fileName, $imageType);
if (!$image)
{
throw new XenForo_Exception(new XenForo_Phrase('image_could_be_processed_try_another_contact_owner'), true);
}
Here is the code for createFromFile() in Image\Abstract.php:
/**
* Creates an image from an existing file.
*
* #param string $fileName
* #param integer $inputType IMAGETYPE_XYZ constant representing image type
*
* #return XenForo_Image_Abstract|false
*/
public static function createFromFileDirect($fileName, $inputType)
{
throw new XenForo_Exception('Must be overridden');
}
...
public static function createFromFile($fileName, $inputType)
{
$class = self::_getDefaultClassName();
return call_user_func(array($class, 'createFromFileDirect'), $fileName, $inputType);
}
Because it looks like createFromFileDirect() is called from createFromFile(), my thought was that a "Must be overriden" error would be thrown, but this doesn't appear to be the case.
Any ideas?
I'm trying to check if a view file really exists according to the third_party paths loaded
Normally, I'd check if a view exists with is_file(APPPATH.'/views/folder/'.$view)
I can retrieve every loaded third_party paths with get_package_paths (thanks to the comment of Tpojka) and then check in their folder views if the file exists,
but I was hoping for a 'direct' check, as if the ->view function would return false instead of redirecting to an error page
$html = $this->load->view($tpl,'',TRUE) ? $this->load->view($tpl,'',TRUE) : $another_template;
Though I realize there might be no other solutions that adding this manual check with a loop through the loaded paths and hide it in a CI_Load Class extension (application/core/MY_Loader) to give the apparance of a direct check in the controller:
EDIT: This is a bad idea, cause view() may return false to CI function that might not be designed for
class MY_Loader extends CI_Lodaer{
public function __construct() {
parent::__construct();
}
public function view($view, $vars = array(), $return = FALSE)
{
foreach( $this->get_package_paths( TRUE ) as $path )
{
// this will only retrieve html from the first file found
if( is_file( $path."/views/".$view ) ) return parent::view($view, $vars, $return);
}
// if no match
return false;
}
}
What I find annoying is that load->view already makes a check through the paths, so this solution will add a second check and increase server consumption..
Well in the end I choose this lukewarm solution :
Instead of extending the function view() for it to return false (and having to deal with it through CI then after !) , I just made a function is_view() in the application/core/MY_Loader.php
I'm not sure MY_Loader is the correct place to place such a function, but so far it did the trick for me ...
(thx Tpojka for the indication)
in application/core/MY_Loader.php
/**
* is_view
*
* Check if a view exists or not through the loaded paths
*
* #param string $view The relative path of the file
*
* #return string|bool string containing the path if file exists
* false if file is not found
*/
public function is_view($view)
{
// ! BEWARE $path contains a beginning trailing slash !
foreach( $this->get_package_paths( TRUE ) as $path )
{
// set path, check if extension 'php'
// (would be better using the constant/var defined for file extension of course)
$path_file = ( strpos($view,'.php') === false ) ? $path."views/".$view.'.php' : $path."views/".$view ;
// this will return the path at first match found
if( is_file( $path_file ) ) return $path."views/";
}
// if no match
return false;
}
and in application/controllers/Welcome.php
$view = "frames/my_html.php";
/*
* the view file should be in
* application/third_party/myapp/views/frames/my_html.php
*
* so far, if the file does not exists, and we try
* $this->load->view($view) will redirect to an error page
*/
// check if view exists and retrieve path
if($possible_path = $this->load->is_view($view))
{
//set the data array
$data = array("view_path"=>$possible_path);
// load the view knowing it exists
$this->load->view($view,$data)
}
else echo "No Template for this frame in any Paths !";
and of course in the view
<h1>My Frame</h1>
<p>
The path of this file is <=?$view_path?>
</p>
How can I extract the following parts using PHP function:
The Domain
The path without the file
The file
The file with Extension
The file without Extension
The scheme
The port
The query
The fragment
(add any other that you think would be useful)
Ex.1
https://stackoverflow.com/users/test/login.php?q=san&u=post#top
The Domain (stackoverflow.com)
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The scheme(https:)
The port(return empty string)
The query(q=san&u=post)
The fragment(top)
Ex: 2 stackoverflow.com/users/test/login.php?q=san&u=post#top
The Domain (stackoverflow.com)
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The scheme(return empty string)
The port(return empty string)
The query(q=san&u=post)
The fragment(top)
Ex: 3 /users/test/login.php?q=san&u=post#top
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 4 /users/test/login?q=san&u=post#top
The path without the file (/users/test/)
The file(login)
The file Extension (return empty string)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 5 login?q=san&u=post#top
The file(login)
The file Extension (return empty string)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 6 ?q=san&u=post
The query(q=san&u=post)
For remaining (return empty string)
I checked parse_url function, but doesn't return what I need. Since, I'm beginner in PHP, it was difficult for me. If you have any idea, please answer.
Thanks in advance.
PHP provides a parse_url function.
This function parses a URL and returns an associative array containing
any of the various components of the URL that are present.
This function is not meant to validate the given URL, it only breaks
it up into the above listed parts. Partial URLs are also accepted,
parse_url() tries its best to parse them correctly.
You can see the test cases executed here.
$urls = array(
"https://stackoverflow.com/users/test/login.php?q=san&u=post#top",
"/users/test/login.php?q=san&u=post#top",
"?q=san&u=post#top",
"login.php?q=san&u=post#top",
"/users/test/login?q=san&u=post#top",
"login?q=san&u=post#top"
);
foreach( $urls as $x ) {
echo $x . "\n";
var_dump( parse_url($x) );
}
I"m using this to locate the root and webroot
<?php
/**
* #brief get paths from the location where it was executed.
*/
class PathHelper {
/**
* #brief This function tries to determine the FileSystem root of the application. (needs to be executed in the root)
* #return string
*/
public static function locateRoot($file) {
$dir = dirname($file);
/** FIX Required for WINDOWS * */
$dir = preg_replace('/\\\\/', '/', $dir);
$dir = preg_replace('/\\\/', '/', $dir);
return $dir;
}
/**
* #brief This function tries to determine the WebRoot. (needs to be executed in the root)
* #return string
*/
public static function locateWebroot($file) {
$docroot = $_SERVER['DOCUMENT_ROOT'];
$dir = dirname($file);
if ($dir == $docroot) {
$webroot = "";
} else {
$webroot = substr_replace($dir, '', 0, strlen($docroot));
}
/** FIX Required for WINDOWS * */
$webroot = preg_replace('/\\\\/', '/', $webroot);
$webroot = preg_replace('/\\\/', '/', $webroot);
return $webroot;
}
}
I set this as a constant so i can use it throughout my application.
For example:
For a menu you can do something like this:
// the requested url
$requestedUrl = $_SERVER['REQUEST_URI'];
// remove the webroot from the requested url
$requestedUrl = str_replace(WEBROOT, "", $_SERVER['REQUEST_URI']);
// take away the / if they still exist at the beginning
$requestedUrl = ltrim($requestedUrl, "/");
Then i got this:
index.php?controller=User&action=overview
This equals to my url of one of my menu items.
You could use explode on this last url to find all the other values you want.
Edit: Its probably better to use parse_url(). I am not used to all the functions in PHP but if nothing works then this is atleast a fallback.
I've run into an issue with the php zip library causing an error 500 if the file is growing to larger than 500MB, probably memory related...
But I tried to cmd line the zip creation, which works well on my server.
<?php
set_time_limit(10000);
// Make Zip name
$zipname = "main_backup.zip";
// Make a zip file
$cmd = `zip -r $zipname * -x filelist.php -x $zipname`;
if($cmd){
echo 'zip created';
}
else{
echo 'failed';
}
unlink(__FILE__);
?>
I know how to exclude files and folders, but is there a way to zip only files based on the modified time using this approach?
I've googled for hours and came up empty.
for the sake of it, here's the code that was creating the error 500. My site is about 1.8GB, it always errors at 500MB~. I should note the error log is blank, so the cause of the error I'm just assuming to be RAM limit problems.
<?php
$zip = new ZipArchive;
$zip_name = "test.zip";
$res = $zip->open($zip_name, ZipArchive::CREATE);
/**
* Real Recursive Directory Iterator
*/
class RRDI extends RecursiveIteratorIterator {
/**
* Creates Real Recursive Directory Iterator
* #param string $path
* #param int $flags
* #return DirectoryIterator
*/
public function __construct($path, $flags = 0) {
parent::__construct(new RecursiveDirectoryIterator($path, $flags));
}
}
/**
* Real RecursiveDirectoryIterator Filtered Class
* Returns only those items which filenames match given regex
*/
class AdvancedDirectoryIterator extends FilterIterator {
/**
* Regex storage
* #var string
*/
private $regex;
/**
* Creates new AdvancedDirectoryIterator
* #param string $path, prefix with '-R ' for recursive, postfix with /[wildcards] for matching
* #param int $flags
* #return DirectoryIterator
*/
public function __construct($path, $flags = 0) {
if (strpos($path, '-R ') === 0) { $recursive = true; $path = substr($path, 3); }
if (preg_match('~/?([^/]*\*[^/]*)$~', $path, $matches)) { // matched wildcards in filename
$path = substr($path, 0, -strlen($matches[1]) - 1); // strip wildcards part from path
$this->regex = '~^' . str_replace('*', '.*', str_replace('.', '\.', $matches[1])) . '$~'; // convert wildcards to regex
if (!$path) $path = '.'; // if no path given, we assume CWD
}
parent::__construct($recursive ? new RRDI($path, $flags) : new DirectoryIterator($path));
}
/**
* Checks for regex in current filename, or matches all if no regex specified
* #return bool
*/
public function accept() { // FilterIterator method
return $this->regex === null ? true : preg_match($this->regex, $this->getInnerIterator()->getFilename());
}
}
foreach (new AdvancedDirectoryIterator('-R *') as $i){
//$fullpath = str_replace(',','',$i->getPath()).'/'.$i->getFilename();
//echo $fullpath.'<br />';
if ($i->isFile() && $i->getFilename()!='filelist.php') {
//echo $i->getFilename() . " " . $i->getMTime() . "<br />";
if($i->getMTime()>='0'){
$array[] = substr($i->getPathname(), 2);
}
}
};
// will output all php files in CWD and all subdirectories
foreach($array as $files) {
$zip_array[] = files;
$zip->addFile($files);
}
$zip->close();
echo 'done';
?>
You can use the -t or -tt option for the zip command and have your modifed date stored as a variable or just pass one in.
-t with the format mmddyyyy for from-date
-tt with the format mmddyyyy for before-date
//Zips up all files in current directory that were dated 08152013 or later
zip -r -t 08152013 $zipname * -x filelist.php -x $zipname
How can I compile a blade template from a string rather than a view file, like the code below:
<?php
$string = '<h2>{{ $name }}</h2>';
echo Blade::compile($string, array('name' => 'John Doe'));
?>
http://paste.laravel.com/ujL
I found the solution by extending BladeCompiler.
<?php namespace Laravel\Enhanced;
use Illuminate\View\Compilers\BladeCompiler as LaravelBladeCompiler;
class BladeCompiler extends LaravelBladeCompiler {
/**
* Compile blade template with passing arguments.
*
* #param string $value HTML-code including blade
* #param array $args Array of values used in blade
* #return string
*/
public function compileWiths($value, array $args = array())
{
$generated = parent::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}
Small modification to the above script.
You can use this function inside any class without extending the BladeCompiler class.
public function bladeCompile($value, array $args = array())
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
For anyone still interested in this, they've added it to Laravel 9
use Illuminate\Support\Facades\Blade;
return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
https://laravel.com/docs/9.x/blade#rendering-inline-blade-templates
I just stumbled upon the same requirement! For me, i had to fetch a blade template stored in DB & render it to send email notifications.
I did this in laravel 5.8 by kind-of Extending \Illuminate\View\View. So, basically i created the below class & named him StringBlade (I couldn't find a better name atm :/)
<?php
namespace App\Central\Libraries\Blade;
use Illuminate\Filesystem\Filesystem;
class StringBlade implements StringBladeContract
{
/**
* #var Filesystem
*/
protected $file;
/**
* #var \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
protected $viewer;
/**
* StringBlade constructor.
*
* #param Filesystem $file
*/
public function __construct(Filesystem $file)
{
$this->file = $file;
$this->viewer = view();
}
/**
* Get Blade File path.
*
* #param $bladeString
* #return bool|string
*/
protected function getBlade($bladeString)
{
$bladePath = $this->generateBladePath();
$content = \Blade::compileString($bladeString);
return $this->file->put($bladePath, $content)
? $bladePath
: false;
}
/**
* Get the rendered HTML.
*
* #param $bladeString
* #param array $data
* #return bool|string
*/
public function render($bladeString, $data = [])
{
// Put the php version of blade String to *.php temp file & returns the temp file path
$bladePath = $this->getBlade($bladeString);
if (!$bladePath) {
return false;
}
// Render the php temp file & return the HTML content
$content = $this->viewer->file($bladePath, $data)->render();
// Delete the php temp file.
$this->file->delete($bladePath);
return $content;
}
/**
* Generate a blade file path.
*
* #return string
*/
protected function generateBladePath()
{
$cachePath = rtrim(config('cache.stores.file.path'), '/');
$tempFileName = sha1('string-blade' . microtime());
$directory = "{$cachePath}/string-blades";
if (!is_dir($directory)) {
mkdir($directory, 0777);
}
return "{$directory}/{$tempFileName}.php";
}
}
As you can already see from the above, below are the steps followed:
First converted the blade string to the php equivalent using \Blade::compileString($bladeString).
Now we have to store it to a physical file. For this storage, the frameworks cache directory is used - storage/framework/cache/data/string-blades/
Now we can ask \Illuminate\View\Factory native method 'file()' to compile & render this file.
Delete the temp file immediately (In my case i didn't need to keep the php equivalent file, Probably same for you too)
And Finally i created a facade in a composer auto-loaded file for easy usage like below:
<?php
if (! function_exists('string_blade')) {
/**
* Get StringBlade Instance or returns the HTML after rendering the blade string with the given data.
*
* #param string $html
* #param array $data
* #return StringBladeContract|bool|string
*/
function string_blade(string $html, $data = [])
{
return !empty($html)
? app(StringBladeContract::class)->render($html, $data)
: app(StringBladeContract::class);
}
}
Now i can call it from anywhere like below:
<?php
$html = string_blade('<span>My Name is {{ $name }}</span>', ['name' => 'Nikhil']);
// Outputs HTML
// <span>My Name is Nikhil</span>
Hope this helps someone or at-least maybe inspires someone to re-write in a better way.
Cheers!
I'm not using blade this way but I thought that the compile method accepts only a view as argument.
Maybe you're looking for:
Blade::compileString()
It's a old question. But I found a package which makes the job easier.
Laravel Blade String Compiler renders the blade templates from the string value. Check the documentation on how to install the package.
Here is an example:
$template = '<h1>{{ $name }}</h1>'; // string blade template
return view (['template' => $template], ['name' => 'John Doe']);
Note: The package is now updated to support till Laravel 6.
I know its pretty old thread, but today also requirement is same.
Following is the way I solved this on my Laravel 5.7 (but this will work with any laravel version greater than version 5), I used the knowledge gained from this thread and few other threads to get this working (will leave links to all threads at the end, if this help up-vote those too)
I added this to my helper.php (I used this technique to add helper to my project, but you can use this function directly as well)
if (! function_exists('inline_view')) {
/**
* Get the evaluated view contents for the given blade string.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
function inline_view($view = null, $data = [], $mergeData = [])
{
/* Create a file with name as hash of the passed string */
$filename = hash('sha1', $view);
/* Putting it in storage/framework/views so that these files get cleared on `php artisan view:clear*/
$file_location = storage_path('framework/views/');
$filepath = storage_path('framework/views/'.$filename.'.blade.php');
/* Create file only if it doesn't exist */
if (!file_exists($filepath)) {
file_put_contents($filepath, $view);
}
/* Add storage/framework/views as a location from where view files can be picked, used in make function below */
view()->addLocation($file_location);
/* call the usual view helper to render the blade file created above */
return view($filename, $data, $mergeData);
}
}
Usage is exactly same as laravel's view() helper, only that now first parameter is the blade string
$view_string = '#if(strlen($name_html)>6)
<strong>{{ $name_html }}</strong>
#else
{{$name_html}}
#endif';
return inline_view($view_string)->with('name_html', $user->name);
return inline_view($view_string, ['name_html' => $user->name]);
References:
https://stackoverflow.com/a/31435824/4249775
https://stackoverflow.com/a/33594452/4249775
Laravel 9 :
use Illuminate\Support\Facades\Blade;
return Blade::render('Your Blade Content {{ $parameter1}}', ['parameter1' => 'Name']);