Good afternoon, I'm looking for an elegant method with passing a namespace, an example:
use App\Services\SuppliersApiParsers\parserName;// I plan to create a bunch of parsers in this folder, each with its own file, i.e. parserName1, parserName2
class SuppliersPrice implements SuppliersPriceInterfaces
{
//..
private function getSuppliers($store_supplier)
{
$parser= SuppliersApiStoreSet::where('store_supplier', $store_supplier)
->first();
$parserFile = $parser->file;
echo parserName::showBasicFunction();
//Но хочу: $parserFile::showSupplierBrandList();
}
}
//I can write example:
$nameFun = 'getSuppliers';
echo self::$nameFun($store_supplier);
//But not :
$nameClass = 'parserName';
use App\Services\SuppliersApiParsers\$nameClass;
All parsers will have typed data as output, but the magic of obtaining them will be hidden in each file. I will pull the names of these files from the database, depending on the needs of the request.
Now I'm thinking about file inclusions, but I don't find that a good option.
I expect a universal mechanism for connecting parsers.
Related
Goal: understand whether this implemented order/logic of operation indeed belongs to the C part of MVC.
Situation: I have nearly finished a simple note-taking site with markdown files. No database is used except for authentication. The lack of database, however, makes it difficult to know if I am neglecting the M of MVC.
Because I did not want to have the .md extension as part of the pretty url, I am heavily relying on the PageController to settle the order of operation.
From the Controller, PageController inherits the constructed filesystem/Flysystem(fs), twig, and the "$app" that processes any of the three scenarios.
It first checks to see if a param.md exists and then whether param is a directory. If none of the above, then it's a new note. In each case, it uses a corresponding method set by the Application ($app) that processes and returns an array (title, breadcrumbs, content/directory listing, new template etc).
<?php
namespace App\Controller;
class PageController extends Controller {
public function Page($param){
$file=$param.'.md';
if ($this->fs->has($file)) {
$data=$this->app->setNote($file);
return $this->app->render('note.twig',$data)
}
elseif ($this->fs->has($param)) {
$data=$this->app->setFolder($param);
return $this->app->render('folder.twig',$data)
}
else {
$data=$this->app->setNew($param);
return $this->app->render('new.twig',$data)
}
}
}
Per "PHP the Right Way":
Controllers handle the request, process the data returned from models and load views to send in the response.
Although my code works and gets the job done, it does not appear to the be right way because the main App is doing the processing. I guess I could just move the Application.php in the Models folder, but would that make it adhere to "right way"? Should I use a Middleware before the PageController gets to work?
It may seem silly to ask about a code that just works, but my goal is to better understand/learn the current wisdom's ways when dealing with flat-files.
Regardless of whether you are 'database-less', the data is being stored / accessed in the .md files.
Access to them should be abstracted to a Model. You should create a File.find object + method, and/or a File.find_or_create. Then
$file = File.find_or_create($param);
$render_type = $file.type . '.twig';
return $this->app->render($render_type, $file.data);
Put all your if logic in the Model.
I am using the Fractal library to transform a Book object into JSON by using a simple transformer:
class BookTransformer extends \League\Fractal\TransformerAbstract
{
public function transform(Book $book)
{
return [
'name' => $book->getName()
// ...
];
}
}
And I am performing the transformation as follows.
$book = new Book('My Awesome Book');
$resource = new \League\Fractal\Resource\Item($book, new BookTransformer());
$fractal = new \League\Fractal\Manager();
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
$json = $fractal->createData($resource)->toJson();
This works great. However, I have certain fields on my Book object that should not always be included, because this depends on the context the transformation is done in. In my particular use case, the JSON returned to AJAX requests from my public website should not include sensitive information, while this should be the case when the data is requested from an admin backend.
So, let's say that a book has a topSecretValue field, which is a string. This field should not be included in one transformation, but should be included in another. I took a look at transformer includes, and played around with it, but this only works with resources. In my case, I need to somehow include different fields (not resources) for different contexts. I have been digging around and could not find anything in the Fractal library that could help me, but maybe I am missing something?
I came up with a working solution, but it is not the prettiest the world has ever seen. By having a BaseBookTransformer that transforms fields that should always be included, I can extend this transformer to add fields for other contexts, e.g. AdminBookTransformer or TopSecretValueBookTransformer, something like the below.
class AdminBookTransformer extends BookTransformer
{
public function transform(Book $book)
{
$arr = parent::transform($book);
$arr['author'] = $book->getTopSecretValue();
return $arr;
}
}
This works fine, although it is not as "clean" as using includes (if it were possible), because I have to actually use a different transformer.
So the question is: is there anything in Fractal that enables me to accomplish this in a simpler/cleaner way, or is there a better way to do it, be it the Fractal way or not?
fetchdata.php - sends the required data
function FetchItems()
{
while($Items = mysql_fetch_object("SELECT * FROM items")) //function is deprecated, would be changing it
{
$TotalItems[] = $Items;
}
return $TotalItems;
}
listitems.php - for user interface and displaying list of items to the user
require_once('fetchdata.php');
$Items = FetchItems();
echo '<table>';
foreach($Items as $ItemDetails)
{
echo '<tr>';
echo '<td>';
echo $ItemDetails->ItemName; // right now the UI developer requires the actual column names to display data but I don't want this
echo '</td>';
echo '<td>';
echo $ItemDetails->ItemQty;
echo '</td>';
echo '<td>';
echo $ItemDetails->ItemCost;
echo '</td>';
echo '</tr>';
}
echo '</table>';
I need to separate the mysql development and the user interface development so as to distribute the workload between separate persons. The UI developer should not require the actual column names/table names/database names for showing data to the user.
But these would only be required by the back-end developer.
From a developer point of view , if in case I wanted to change the column name of my table like , ItemQty to ItemAvailableQty then I would have to ask the UI developer to edit the UIe file i.e. listitems.php, but I want, that only the back-end file which is fetchdata.php should be edited and the UI file should not be touched for such database changes. Whatever be the changes in the database, those changes should be independent of the UI file, the UI file should not be aware of/require those back-end changes, i.e. changes would be invisible for the UI file. The UI file would only be edited if and only if there are some UI alterations.
What steps shall I take to achieve this and how? Do I need to create views/stored procedures etc.? Or do I have to go for MVC framework, if yes, then how?
You are not required to use MVC framework, but it will be better than reinventing the wheel. In the global concept of MVC you have a View object which contains the needed variables for your UI to run. It depends on the way you are implementing it. In most of the cases in your View object you would need a magic set method, which assigns values to non existent variables.
class View {
public function __set($name, $value) {
$this->$name = $value
}
public function __get($name) {
return $this->$name;
}
}
In the very simpliest way, if you try to set value to a non-existent property of the object of View, it will create the property on the fly.
Then in your backend interaction you have a global class that holds the View object, let's not call it application, but Controller.
class Controller {
protected $view;
public function __construct() {
$this->view = new View();
}
}
No magic, just a shared property through all of your backend interaction that holds the View object.
You now have your FetchItems() method in a class, let's call it, Items.
So, you create a controller which uses the database operation (in the MVC, it would be a Model) and assigns return values to the magic properties of View.
class ItemsController extends Controller {
public function ItemsList() {
$items = new Items();
foreach ($items->FetchItems() as $item) {
$this->view->ItemName[] = $item->ItemName;
$this->view->ItemQty[] = $item->ItemQty;
$this->view->ItemCost[] = $item->ItemCost[];
}
}
So here, we created 3 array variables in the View object from the return data of your Items model. It won't be the best approach, since the UI developer should use some backend functions to interact with them, so you might want to create one big array which holds them like $this->view->Items['name'] = $item->ItemName, $this->view->Items['Qty'] = $item->ItemQty, so you will only send the variable Items to the view.
Now, if you change a column name, you'd need to do changes only in your business logic, where you would make $this->view->Items['qty'] = $item->ItemAvailableQty; but in the UI part, the $this->Items['qty'] will remain.
This of course does not show how to implement whole MVC, it just shows the logic you could follow. You, of course, could use existent MVC framework, or just alter your application to have shared variables, so your UI part will recieve information from an object, which can use created on the fly properties. So once assigned value to an internal property, you will only need to change the assignation, but not the real property which is used in the template.
For example, out of the strict OOP or Frameworks, you can assign values to internal properties of the class that holds FetchItems() method, so your UI developer will use them, instead of the raw returned database data.
But, at the end, wouldn't this take you more time and does it worth it, you will make millions of assignations to view variables, just to not make your UI developer care about the columns. A database table is not meant to have its columns changed while in exploitation. Even more, if you already have backend around this table.
You could use a template engine like Smarty or Twig. They ensure a clean separation between your application's design and logic. For an example of how this is achieved, I'd suggest you take a look at Smarty's crash course: http://www.smarty.net/crash_course
It's even possible to combine such a template engine with an MVC framework.
I have a config file which is written in PHP as below,
<?php
class WebConfig {
public static $WEBPATH = '/customers';
public static $ACCOUNTPATH = '/empaccountpath';
public static $INFO = '/accountInfo';
const ACCOUNT_STATUS = '/account_status';
const ENABLE_SEARCH = '/enable_search';
}
?>
So I would like to develop an interface in PHP, which has ability to edit the file values such as $WEBPATH, $ACCOUNTPATH and const values.
Simple PHP editor script will do the above work. But it doesn't check the syntax.
Please suggest how to do it in PHP efficiently.
Better solutions
Many other configuration storage formats are better suited for this kind of thing. Look into php file returns array, ini, json, xml or yaml.
"PHP file returns array" is a simple PHP file which looks like this
return(
array(
'config_key' => 'config_value'
)
);
?>
The return value of this file can be retrieved by your code when you're including it: $x = include('file.php'); and $x will have the values in the array.
INI is simple & intuitive to read or write for humans. It has a limited structure. PHP can read it with one function but it has write capabilities only in a separate (non-default) package. Which means you have to generate the ini files yourself.
JSON is "fairly" simple & intuitive to read or write for humans. It has a flexible structure extensible structure. PHP can read and write to a JSON file using only a couple of functions. Unfortunately PHP does not retain JSON pretty-print formatting so after you overwrite a file, it'll be all in one line, harder to read after that.
XML is simple & intuitive to read for humans, it can be very informative because it's quite verbose about what everything is. I has a structure almost as flexible as JSON and it is extensible. PHP can read and write to an XML but doing so means using a handful of lines of code (5-10 for simple stuff).
YAML is another option which is easy to read and write for humans, PHP doesn't have direct YAML support but there are other options (see below). It's structure is flexible and extensible. For me personally understanding YAML has been less intuitive.
Zend_Config can be used as an interface to reading/writing any of the above formats and can be used to abstract the file format itself and offer your application a format-agnostic way of handling configurations.
You could also use the database to store your configuration or a separate SQLite database dedicated to storing configurations in general -- this is usually used when many configurations need to be retained in a fine grained searchable format that allows various types of layered overriding (ex.: general defaults, controller defaults, action defaults, particular case defaults).
How to do it in PHP
You don't need to create a language parser as #geomagas said. Using include or require is enought, the PHP interpreter will load the "new" class into memory and ensure it is available.
All you need to do is create a template file in which to replace some values such as:
<?php
class WebConfig {
public static $WEBPATH = '$_replace_webpath';
public static $ACCOUNTPATH = '$_replace_accountpath';
public static $INFO = '$_replace_info';
const ACCOUNT_STATUS = '$_replace_account_status';
const ENABLE_SEARCH = '$_replace_enable_search';
}
And then load read the file, and replace it with the current values such as:
$config_template = file_get_contents('/path/to/config/template.php.template');
str_replace(
array('$_replace_webpath' ... ),
array('/customers' ... ),
$config_template
);
PrestaShop uses PHP files for configuration. It rewrites them when needed.
Directly to access the PHP Class file too danger....Many security issues....
Could we use simple html form to let user to edit.
Using JSON format to store into file.
PHP encode & decode by json_encode() & json_decode(), when save & read the value.
I'm trying to create my own xml sitemap. Everything is done except for the part that I thought was going to be the easiest. How do you get a list of all the pages on the site? I have a bunch of views in a /site folder and a few others. Is there a way to explicitly request their URLs or perhaps via the controllers?
I do not want to make use of an extension
You can use reflection to iterate through all methods of all your controllers:
Yii::import('application.controllers.*');
$urls = array();
$directory = Yii::getPathOfAlias('application.controllers');
$iterator = new DirectoryIterator($directory);
foreach ($iterator as $fileinfo)
{
if ($fileinfo->isFile() and $fileinfo->getExtension() == 'php')
{
$className = substr($fileinfo->getFilename(), 0, -4); //strip extension
$class = new ReflectionClass($className);
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
{
$methodName = $method->getName();
//only take methods that begin with 'action', but skip actions() method
if (strpos($methodName, 'action') === 0 and $methodName != 'actions')
{
$controller = lcfirst(substr($className, 0, strrpos($className, 'Controller')));
$action = lcfirst(substr($methodName, 6));
$urls[] = Yii::app()->createAbsoluteUrl("$controller/$action");
}
}
}
}
You need to know what content you want to include in your sitemap.xml, I don't really think you want to include ALL pages in your sitemap.xml, or do you really want to include something like site.com/article/edit/1 ?
That said, you may only want the result from the view action in your controllers. truth is, you need to know what you want to indexed.
Do not think in terms of controllers/actions/views, but rather think of the resources in your system that you want indexed, be them articles, or pages, they are all in your database or stored somehow, so you can list them, and they have a URI that identifies them, getting the URI is a matter of invoking a couple functions.
There are two possiblities -
Case 1:
You are running a static website then you can find all your HTML inside 1 folder - protected/views/site/pages
http://www.yiiframework.com/wiki/22/how-to-display-static-pages-in-yii/
Case 2:
Website is dynamic. Tasks such as generating and regenerating Sitemaps can be classified into background tasks.
Running background taks can be achieved by emulating the browser which is possible in linux using - WGET, GET or lynx commands
Or, You can create a CronController as a CConsoleCommand. How to use Commands in YII is shown in link below -
http://tariffstreet.com/yii/2012/04/implementing-cron-jobs-with-yii-and-cconsolecommand/
Sitemap is an XML which lists your site's URL. But it does more than that.
It helps you visualize the structure of a website , you may have
category
subcategories.
While making a useful extension, above points can be kept into consideration before design.
Frameworks like Wordpress provide way to generate categorical sitemap.
So the metadata for each page is stored from before and using that metadata it discovers and group pages.
Solution by Reflection suggested by #Pavle is good and should be the way to go.
Consider there may be partial views and you may or may not want to list them as separate links.
So how much effort you want to put into creating the extension is subject to some of these as well.
You may either ask user to list down all variables in config fie and go from there which is not bad or you have to group pages and list using some techniques like reflection and parsing pages and looking for regex.
For ex - Based on module names you can group them first and controllers inside a module can form sub-group.
One first approach could be to iterate over the view files, but then you have to take into account that in some cases, views are not page destinations, but page sections included in another pages by using CController::renderPartial() method. By exploring CController's Class Reference I came upon the CController::actions() method.
So, I have not found any Yii way to iterate over all the actions of a CController, but I used php to iterate over all the methods of a SiteController in one of my projects and filter them to these with the prefix 'action', which is my action prefix, here's the sample
class SiteController extends Controller{
public function actionTest(){
echo '<h1>Test Page!</h1></p>';
$methods = get_class_methods(get_class($this));
// The action prefix is strlen('action') = 6
$actionPrefix = 'action';
$reversedActionPrefix = strrev($actionPrefix);
$actionPrefixLength = strlen($actionPrefix);
foreach ($methods as $index=>$methodName){
//Always unset actions(), since it is not a controller action itself and it has the prefix 'action'
if ($methodName==='actions') {
unset($methods[$index]);
continue;
}
$reversedMethod = strrev($methodName);
/* if the last 6 characters of the reversed substring === 'noitca',
* it means that $method Name corresponds to a Controller Action,
* otherwise it is an inherited method and must be unset.
*/
if (substr($reversedMethod, -$actionPrefixLength)!==$reversedActionPrefix){
unset($methods[$index]);
} else $methods[$index] = strrev(str_replace($reversedActionPrefix, '', $reversedMethod,$replace=1));
}
echo 'Actions '.CHtml::listBox('methods', NULL, $methods);
}
...
}
And the output I got was..
I'm sure it can be furtherly refined, but this method should work for any of the controllers you have...
So what you have to do is:
For each Controller: Filter out all the not-action methods of the class, using the above method. You can build an associative array like
array(
'controllerName1'=>array(
'action1_1',
'action1_2'),
'controllerName2'=>array(
'action2_1',
'action2_2'),
);
I would add a static method getAllActions() in my SiteController for this.
get_class_methods, get_class, strrev and strlen are all PHP functions.
Based on your question:
1. How do you get a list of all the pages on the site?
Based on Yii's way of module/controller/action/action_params and your need to construct a sitemap for SEO.
It will be difficult to parse automatically to get all the urls as your action params varies indefinitely. Though you could simply get controller/action easily as constructed by
Pavle Predic. The complexity comes along when you have customized (SERF) URL rules meant for SEO.
The next best solution is to have a database of contents and you know how to get each content via url rules, then a cron console job to create all the urls to be saved as sitemap.xml.
Hope this helps!