I'm a Frontend developer and I've decided to expand my knowledge learning PHP. I'm still learning the syntax / ways of accomplishing stuff so please bear with me.
As I began working on my first PHP project I realised that I needed to create a path map just to keep things clean and DRY.
This is what my php file structure looks like by now:
That is easy to represent using a JSON like data structure, but I've found a embarrassing hard time trying to achieve that using PHP data types ( I'm still learning the syntax / ways of doing things ).
I've been reading a bit and I decided to use some associative arrays,
I've come up with this solution, which works but I wanted to check if a simpler solution is possible. ( I'm aiming to learn the best practices of PHP )
See:
$paths = array(
'dirs' => array(
'base' => '/php/'
)
);
$paths['dirs']['common'] = $paths['dirs']['base'] . 'common/';
$paths['dirs']['home'] = $paths['dirs']['base'] . 'home/';
$paths['files'] = array(
'home' => $paths['dirs']['home'] . 'home.php',
'header' => $paths['dirs']['common'] . 'header.php',
'scripts' => $paths['dirs']['common'] . 'scripts.php',
'footer' => $paths['dirs']['common'] . 'footer.php',
'core' => $paths['dirs']['base'] . 'core.php',
'business-variables' => $paths['dirs']['base'] . 'business-variables.php'
);
Am I doing bad practices here?
Is there a better / simpler / standard way of doing this?
If you need to work with files - the best and easiest way is use DirectoryIterator. In this case each file will be object and you can use his methods. Here a small example:
// or '/php/' in your case
$path = '/';
foreach (new DirectoryIterator($path) as $file) {
if ($file->isDot()) continue;
if ($file->isDir()) {
print $file->getFilename() . '<br/>';
}
}
Provided you can't just read the folder structure using DirectoryIterator...
For readability I'd do something like this:
class FileStructure {
const BASE = '/php/';
const COMMON = 'common/%s';
const HOME = 'home/%s';
public function Paths(){
return array(
'home' => $this->Home('home.php'),
'header' => $this->Common('header.php')
);
}
private function Common($file = null){
return sprintf(static::BASE.static::COMMON, $file);
}
private function Home($file = null){
return sprintf(static::BASE.static::HOME, $file);
}
}
Related
The custom route doesn't work correctly and always routes to user.htm
index.php
$routes = [
"/" => "index.htm",
"/user/id=#id" => "user.htm","/user/#id" => "user.htm",
];
foreach ($routes as $path => $file) {
$f3->route("GET ".$path,
function($f3){
global $file,$path;
echo View::instance()->render($file);
}
);
}
try this:
$routes = [
"/" => "index.htm",
"/user/id=#id" => "user.htm",
"/user/#id" => "user.htm",
];
foreach ($routes as $path => $file)
{
$f3->route("GET " . $path,
function ($f3) use ($file)
{
echo View::instance()->render($file);
}
);
}
The answer from Bryan Velastegui is the correct one. But here's why your code didn't work:
$f3->route() maps each route URI to a function (called the "route handler"), without executing it.
the foreach loop stores successively the following values into the $file variable: index.html, user.htm and user.htm (again). Therefore, at the end of the loop, $file holds user.htm.
once you call $f3->run(), the framework executes the route handler matching the current route, which itself refers to the global $file variable, holding user.htm.
Generally, you shouldn't be using the global keyword. That will just create unexpected issues, just as the one you've faced. Also that doesn't help for code maintainability.
I advise you to read the docs about the use keyword to understand how Bryan Velastegui's code is working.
SOLVED Thanks to #JPR and tuned-up thanks to #PeterM
/* The only dependance is when */ class NuSoap extends CApplicationComponent
v-Below, the initial question -v
I would like to know how to create a basic extenstion in yii 1.1.13 using nusoap 0.9.5?
My simple code looks like this :
<?php
require("libs/nusoap-0.9.5/lib/nusoap.php");
// namespace
$ns = "https://my-namespace-site";
// client
$client = new soapclient('https://ip-to-webservice-server');
// header
$headers = "<credentials><ns1:username xmlns:ns1=\"$ns\">username</ns1:username>
<ns2:password xmlns:ns2=\"$ns\">password</ns2:password></credentials>";
$client->setHeaders($headers);
// searching
$params = array(
'local_user_array' => array(
'limit' => 10
)
);
$result = $client->call('local_users_search', $params, $ns );
if( $client->getError() ) {
echo $client->getError();
}
else {
foreach( $result['data'] as $offer ) {
echo "<div>".$offer['firstname']."</div>";
}
}
?>
My code works perfectly. Now, what coud I do to use $result in yii to be able to show results in a view?
The best answer will be a concrete example with file structure and code plus meaningful explanations.
Any help would be greatly appreciated. Thanks for your help in advance. I'm looking forward to it ;-)
PS: please do not reference any links to yiiframework site because it doesn't help much as I also know how to search.
Make a class that extends from CApplicationComponent.
class NuSoap extends CApplicationComponent
{
protected $params = array();
protected $client, $ns;
public function init() {
require("libs/nusoap-0.9.5/lib/nusoap.php");
$this->client = new soapclient('https://ip-to-webservice-server');
$this->ns = "https://my-namespace-site";
}
public function getResults() {
$results = $this->client->call(
'local_users_search',
$this->params,
$this->ns
);
return $results;
}
public function setParams(array $params) {
$this->params = $params;
}
// whatever other methods you need for it to work
}
Then in your main config file, under the components array:
array(
'nuSoap' => array(
'class' => 'application.components.NuSoap' // name your class NuSoap.php
)
......
)
Make sure the application/components or application/extensions directory is imported in the main.php config file as well. Put your class file in NuSoap.php in the application/components or applcation/extensions directory.
You can now refer to your component anywhere in your Yii app:
Yii::app()->nuSoap->setParams($params);
$results = Yii::app()->nuSoap->getResults();
This should be plenty to get you started in the right direction. The Yii documentation would be very helpful in understanding how application components work, but since you don't want to read it you'll just have to guess on some things. If you want to use Yii it makes absolutely no sense to avoid reading the documentation.
My dream is to include a php file in a theme which checks if a set of plugins are installed, and installs the ones which are not. Kind of like a set of dependencies for the theme, but also just a good way to package theme development to include a set of good plugins.
My questions...
Is there something like this in existence?
Is it possible to achieve from a single php file in a theme folder?
Are there any obvious pit-falls or problems with this approach?
How would I go about achieving this?
is it possible to enumerate installed plugins from within theme folder?
is it possible to download and place plugin files in the plugins folder?
is it possible to activate plugins from within theme directory?
07/06/2018 EDIT: If you are coming across this answer, the code highlighted below is extremely outdated and insecure and should not be used in any capacity outside of experimentation on a local server. If you are looking for a more modern solution for plugin management, consider installing Wordpress via Composer and Bedrock
I would advise NOT programatically checking for the existence of certain plugins, downloading, installing, and activating them from within any theme file. You have to consider that the check will be run every time the given page is loaded, and can result in a lot of superfluous code and unnecessary activity.
Instead, my advice would be to package any plugins on which your theme depends as a part of the theme itself, and NOT as a plugin. Plugins should be installed at the discretion of the user. If a theme depends on a plugin to function properly or efficiently, then it really should be packaged and downloaded with the theme itself.
But to answer your questions directly:
Probably. It is certainly possible to do.
Yes.
See the above. You potentially run into more issues by constantly checking for plugins and running a series of actions based on those conditions rather than just including everything needed.
Plenty of research
Probably
Yes
Yes
I cannot stress enough, however, that the purpose of a PLUGIN is to give the user the option to extend a given theme's capabilities. If your theme's capabilities DEPEND on existing plugins, then you really REALLY should include all the files when somebody downloads your theme.
Though if you feel that your approach benefits your theme in ways that I might be missing, feel free to write it however you like.
COMPLETE ANSWER: I decided to help create a proof of concept for you, because I got bored and curious. Much of this should be self explanatory. Add these functions:
function mm_get_plugins($plugins)
{
$args = array(
'path' => ABSPATH.'wp-content/plugins/',
'preserve_zip' => false
);
foreach($plugins as $plugin)
{
mm_plugin_download($plugin['path'], $args['path'].$plugin['name'].'.zip');
mm_plugin_unpack($args, $args['path'].$plugin['name'].'.zip');
mm_plugin_activate($plugin['install']);
}
}
function mm_plugin_download($url, $path)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
curl_close($ch);
if(file_put_contents($path, $data))
return true;
else
return false;
}
function mm_plugin_unpack($args, $target)
{
if($zip = zip_open($target))
{
while($entry = zip_read($zip))
{
$is_file = substr(zip_entry_name($entry), -1) == '/' ? false : true;
$file_path = $args['path'].zip_entry_name($entry);
if($is_file)
{
if(zip_entry_open($zip,$entry,"r"))
{
$fstream = zip_entry_read($entry, zip_entry_filesize($entry));
file_put_contents($file_path, $fstream );
chmod($file_path, 0777);
//echo "save: ".$file_path."<br />";
}
zip_entry_close($entry);
}
else
{
if(zip_entry_name($entry))
{
mkdir($file_path);
chmod($file_path, 0777);
//echo "create: ".$file_path."<br />";
}
}
}
zip_close($zip);
}
if($args['preserve_zip'] === false)
{
unlink($target);
}
}
function mm_plugin_activate($installer)
{
$current = get_option('active_plugins');
$plugin = plugin_basename(trim($installer));
if(!in_array($plugin, $current))
{
$current[] = $plugin;
sort($current);
do_action('activate_plugin', trim($plugin));
update_option('active_plugins', $current);
do_action('activate_'.trim($plugin));
do_action('activated_plugin', trim($plugin));
return true;
}
else
return false;
}
... and then execute like so:
$plugins = array(
array('name' => 'jetpack', 'path' => 'http://downloads.wordpress.org/plugin/jetpack.1.3.zip', 'install' => 'jetpack/jetpack.php'),
array('name' => 'buddypress', 'path' => 'http://downloads.wordpress.org/plugin/buddypress.1.5.5.zip', 'install' => 'buddypress/bp-loader.php'),
array('name' => 'tumblr-importer', 'path' => 'http://downloads.wordpress.org/plugin/tumblr-importer.0.5.zip', 'install' => 'tumblr-importer/tumblr-importer.php')
);
mm_get_plugins($plugins);
'name' can be anything, as it serves to be more of a temporary value. 'path' is exactly what it looks like, and is the direct URL to the zip file on the Wordpress server. The 'install' value is simply the path to the main PHP script that has all of the plugin information. You will have to know the layout of that particular plugin directory in order to fill out this information, as this is also required for the activation hack to work.
Activation function was found here (credit to sorich87): https://wordpress.stackexchange.com/questions/4041/how-to-activate-plugins-via-code
WARNING: This is by no means a very safe way to do things. I actually think that this can be abused quite easily, so our best bet is to use this as our baseline and try and improve from there.
If you should decide to use this approach, all I ask is that I'm credited with the initial overall script, along with sorich87 for his activation process may God have mercy on your soul.
07/06/2018 EDIT: Seriously, don't do this. By today's standards, this code is hot garbage. Plugin management should be done through Composer and Bedrock.
Inspired by the comment from Jamie Dixon I inspected how Wordpress works.
The process can be seen in /wp-admin/update.php from line 93. A short version could be made like this:
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); //for plugins_api..
$plugin = 'plugin-name';
$api = plugins_api( 'plugin_information', array(
'slug' => $plugin,
'fields' => array(
'short_description' => false,
'sections' => false,
'requires' => false,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'last_updated' => false,
'added' => false,
'tags' => false,
'compatibility' => false,
'homepage' => false,
'donate_link' => false,
),
));
//includes necessary for Plugin_Upgrader and Plugin_Installer_Skin
include_once( ABSPATH . 'wp-admin/includes/file.php' );
include_once( ABSPATH . 'wp-admin/includes/misc.php' );
include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact('title', 'url', 'nonce', 'plugin', 'api') ) );
$upgrader->install($api->download_link);
If you don't want the feedback displayed you should create a custom Skin class. For example:
$upgrader = new \Plugin_Upgrader( new Quiet_Skin() );
class Quiet_Skin extends \WP_Upgrader_Skin {
public function feedback($string)
{
// just keep it quiet
}
}
Normally I use the Zend Framework and this is something I miss in Lithium. Partials. There is a render method in the view where you can use 'elements' which is the closest I got.
<?php $this->_render('element', 'form); ?>
This does work, however it requires that the form.html.php file is in the /views/elements folder. Is it possible to let it search in another path? Like /views/users/ so it gets the file /views/users/form.html.php.
I have tried the following, since I found out that the render method does accept an options argument wherein you can specify a path. So I made an Helper to fix this problem for me.
namespace app\extensions\helper;
use lithium\template\TemplateException;
class Partial extends \lithium\template\Helper
{
public function render($name, $folder = 'elements', $data = array())
{
$path = LITHIUM_APP_PATH . '/views/' . $folder;
$options['paths']['element'] = '{:library}/views/' . $folder . '/{:template}.{:type}.php';
return $this->_context->view()->render(
array('element' => $name),
$data,
$options
);
}
}
However it still only searches in the /view/elements folder, not in the path I specified.
Is there something I am doing wrong?
Why using plugins when this stuff can hopefully be done by Lithium :-)
I don't know Zend, but here is an exemple to configure elements default paths differently, to load them from the related view folder, instead of a shared path.
And let's add one more thing: we want to differentiate elements/partials from a normal view, by appending un underscore to the name of the file (mimic Rails partials)
First, reconfigure Media during the bootstrap process (config/bootstrap/media.php)
Media::type('default', null, array(
'view' => 'lithium\template\View',
'paths' => array(
'layout' => '{:library}/views/layouts/{:layout}.{:type}.php',
'template' => '{:library}/views/{:controller}/{:template}.{:type}.php',
'element' => array(
'{:library}/views/{:controller}/_{:template}.{:type}.php',
'{:library}/views/elements/{:template}.{:type}.php'
)
)
));
Then, use it
Suppose a controller Documents. Call on a view:
<?= $this->_render('element', 'foo', $data, array('controller' => 'documents')); ?>
This will look for a file inside views/documents/_foo.html.php and if doesn't exists, fallback to /views/elements/foo.html.php
This kind of simple re-configuration of framework defaults, can be done in Lithium for a bunch of stuffs (default controllers paths to create namespaces, views paths, libraries, etc ...)
One more example to re-maps your template paths so you can have stuff like pages/users_{username}.php instead of the Lithium default:
https://gist.github.com/1854561
Fixed it. Works like a charm. Zend like Partials in Lithium.
<?php
namespace app\extensions\helper;
use lithium\template\View;
class Partial extends \lithium\template\Helper
{
public function render($name, $folder = 'elements', array $data = array())
{
$view = new View(array(
'paths' => array(
'template' => '{:library}/views/' . $folder . '/' . $name . '.{:type}.php'
)
));
return $view->render('all', $data);
}
}
Can be used in templates like:
<?php echo $this->partial->render('filename', 'foldername', compact('foo', 'bar')); ?>
There is a plugin for partials. https://github.com/dmondark/li3_partials
Ok here is a method I use for initializing models in my controller actions:
protected $_tables = array();
protected function _getTable($table)
{
if (false === array_key_exists($table, $this->_tables)) {
include APPLICATION_PATH . '/modules/'
. $this->_request->getModuleName() . '/models/' . $table . '.php';
$this->_tables[$table] = new $table();
echo 'test ';
}
return $this->_tables[$table];
}
Then when I call the _getTable() method two times (for example once in init() method and once in the controller action) it prints:
test test test test test test
On top of the page. Shouldn't it just return the object from the _tables array() because of the array_key_exists() check? In other words shouldn't the part inside the array_key_exists() function get executed only once when the method is called multiple times?
UPDATE:
So the problem is this - for some reason the layout gets printed twice (so it's layout printed and inside the layout where there is layout()->content; ?> it prints the layout again). I have no idea why it does this as it worked well on the previous server and also on localhost.
In the snippet you show:
protected $this->_tables = array();
This is not valid syntax, it should be:
protected $_tables = array();
Also, why not just use include_once and let PHP handle this for you? Alternatively, you could use the Zend_Loader. Don't reinvent the wheel.
What you are really looking for is the loading of module based resources. Instead of re-inventing the wheel, why not just use the (module) resource autoloaders of ZF? See the documentation at:
http://framework.zend.com/manual/en/zend.loader.autoloader-resource.html
When you use Zend_Application (I'm assuming you don't), you get these automatically. If you don't you could do something like
$loaders = array();
$frontController = Zend_Controller_Front::getInstance();
foreach($frontController->getControllerDirectory() as $module => $directory) {
$resourceLoader = new Zend_Application_Module_Autoloader(array(
'namespace' => ucfirst($module) . '_',
'basePath' => dirname($directory),
));
$resourceLoader->addResourceTypes(array(
'table' => array(
'path' => 'models/',
'namespace' => 'Table'
));
$loaders[$module] = $resourceLoader;
}
//build array of loaders
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setAutoloaders($loaders);
//set them in the autoloader
This approach is a bit naive, but it should give you nice autoloading.