I have a following situation. I have a Model A with following properties:
id int
name varchar(255)
parent_id int (references same Model A).
Now, I need to render Tree View using that ModelA. Of course, I could just load all data, sort it properly by parent_id and "render it" using traditional string sticking. e.g.
class Model_A extends Model_Table {
...
function render_branch($nodes, $parent){
if (!isset($nodes[$parent])){
return null;
}
$out = "<ul>";
foreach ($nodes[$parent] as $node){
$out .= "<li>" . $node["name"];
$out .= $this->render_branch($nodes, $node["id"]);
$out .= "</li>";
}
return $out;
}
function init(){
parent::init();
$nodes = array(); // preload from db and arrange so that key = parent and content is array of childs
$this->template->set("tree", $this->render_branch($nodes, 0));
}
}
now, I would instead like to use atk4 native lister/smlite template parser for the purpose. but, if you try to do that, then you would end up with nasty lister, where in format row, you would anyway try to substitute the specific tag with output from other lister which in fact you would have to destruct to void runtime memory overflows.
any suggestions?
p.s.
code above is not tested, just shows concept
thanks!
Okay, right time had come and proper add-on has been created. To use it, get your add ons and atk4 up-to-dated and follow this article to get to know how.
http://www.ambienttech.lv/blog/2012-07-06/tree_view_in_agile_toolkit.html
As per Jancha's comment
okay, after spending some time looking at possible options, I found that
the easiest thing to do in this particular case was to use above mentioned example.
The only way to make it more native would be to use external template for
nodes and use smite and clone region + render to move html outside t o
template. apart from that, usage of traditional lister did not seem to
be efficient enough. so, atk4 guys, follow up with query tree view
plugin and create proper backend! it would be cool. thanks,j
.
Related
I'm using the following code to create a DOMDocument and validate it against an external xsd file.
<?php
$xmlPath = "/xml/some/file.xml";
$xsdPath = "/xsd/some/schema.xsd";
$doc = new \DOMDocument();
$doc->loadXML(file_get_contents($xmlPath), LIBXML_NOBLANKS);
if (!$doc>schemaValidate($xsdPath)) {
throw new InvalidXmlFileException();
}
Update 2 (rewritten question)
This works fine, meaning that if the XML doesn't match the definitions of XSD it will throw a meaningful exception.
Now, I want to retrieve information from the DOMDocument using Xpath. It works fine aswell, however, from this point on the DOMDocument is completely detached from the XSD! For example, if I have a DOMNode I cannot know whether it is of type simpleType or type complexType. I can check whether the node has child (hasChild()) nodes, but this is not the same. Also, there is tons of information more in the XSD (like, min and max number of occurrence, etc).
The question really is, do I have to query the XSD myself or is there a programmatic way of asking those kind of questions. I.e. is this DOMNode a complex or simple type?
In another post it was suggested "to process the schema using a real schema processor, and then use its API to ask questions about the contents of the schema". Does XPath has an API to retrieve information of the XSD or is there a different convenient way with DOMDocument?
For the record, the original question
Now, I wanted to proceed to parse information from the DOMDocument using XPath. To increase the integrity of my data I'm storing to a database and giving meaningful error message to the client I wanted to constantly use the schema information to validate the queries. I.e. I wanted to validate fetched childNodes against allowed child nodes defined in the xsd. I wanted to that by using XPath on the xsd document.
However, I sumbled across this post. It basically sais it is a kind of kirky way to that yourself and you should rather use a real schema processor and use its API to make the queries. If I understand that right, I'm using a real schema processor with schemaValidate, but what is meant by using its API?
I kind of guessed already I'm not using the schema in a correct way, but I have no idea how to research a proper usage.
The question
If I use schemaValidate on the DOMDocument is that a one-time validation (true or false) or is it tied to the DOMDocument for longer then? Precisely, can I use the validation also for adding nodes somehow or can I use it to select nodes I'm interested in as suggested by the referenced SO post?
Update
The question was rated unclear, so I want to try again. Say I would like to add a node or edit a node value. Can I use the schema provided in the xsd so that I can validate the user input? Originally, in order to do that I wanted to query the xsd manually with another XPath instance to get the specs for a certain node. But as suggested in the linked article this is not best practice. So the question would be, does the DOM lib offer any API to make such a validation?
Maybe I'm overthinking it. Maybe I just add the node and run the validation again and see where/why it breaks? In that case, the answer of the custom error handling would be correct. Can you confirm?
Your question is not very clear, but it sounds like you want to get detailed reporting about any schema validation failures. While DomDocument::validateSchema() only returns a boolean, you can use internal libxml functions to get some more detailed information.
We can start with your original code, only changing one thing at the top:
<?php
// without this, errors are echoed directly to screen and/or log
libxml_use_internal_errors(true);
$xmlPath = "file.xml";
$xsdPath = "schema.xsd";
$doc = new \DOMDocument();
$doc->loadXML(file_get_contents($xmlPath), LIBXML_NOBLANKS);
if (!$doc->schemaValidate($xsdPath)) {
throw new InvalidXmlFileException();
}
And then we can make the interesting stuff happen in the exception which is presumably (based on the code you've provided) caught somewhere higher up in the code.
<?php
class InvalidXmlFileException extends \Exception
{
private $errors = [];
public function __construct()
{
foreach (libxml_get_errors() as $err) {
$this->errors[] = self::formatXmlError($err);
}
libxml_clear_errors();
}
/**
* Return an array of error messages
*
* #return array
*/
public function getXmlErrors(): array
{
return $this->errors;
}
/**
* Return a human-readable error message from a libxml error object
*
* #return string
*/
private static function formatXmlError(\LibXMLError $error): string
{
$return = "";
switch ($error->level) {
case \LIBXML_ERR_WARNING:
$return .= "Warning $error->code: ";
break;
case \LIBXML_ERR_ERROR:
$return .= "Error $error->code: ";
break;
case \LIBXML_ERR_FATAL:
$return .= "Fatal Error $error->code: ";
break;
}
$return .= trim($error->message) .
"\n Line: $error->line" .
"\n Column: $error->column";
if ($error->file) {
$return .= "\n File: $error->file";
}
return $return;
}
}
So now when you catch your exception you can just iterate over $e->getXmlErrors():
try {
// do stuff
} catch (InvalidXmlFileException $e) {
foreach ($e->getXmlErrors() as $err) {
echo "$err\n";
}
}
For the formatXmlError function I just copied an example from the PHP documentation that parses the error into something human readable, but no reason you couldn't return some structured data or whatever you like.
I think what you're looking for is the PSVI (post schema validation infoset), see this answer for some references.
An other option would be to use XPath2 that has operators to check schema types.
I don't know if there are libraries in PHP that allows you to get PSVI or perform XPath2 queries, in Java there is Xerces for PSVI and Saxon for XPath2
For example With Xerces is possible to cast a DOM Element to a Xerces ElementPSVI in order to get schema informations of an Element.
I can warn that using XPath on the schema (as you were doing) will work only for simple cases since the XML of the schema is very different from the actual schema model (assembled schema) that is a graph of components with properties that are yes calculated from the XML declaration (schema file) but with very complex rules that are almost impossible to recreate with XPath.
So you need at least the PSVI or to make XPath2 queries but, in my experience, obtaining decent validation for application users from an XML schema is difficult.
What are you trying to achieve ?
Intro
Hello there,
i want to set up a big data structure that goes like this:
[products]
.[cat_1]
.[cat_1.1]
.[item-A] //type 1
.[item-B] //type 1
. ...
.[cat_1.2]
.[item-X] //type 2
.[cat_2]
.[item-Y] //type 3
So far so good.
Goal
Since the site shall be kind of a catalog the URL would be for example:
www.example.com/products/cat_1/.../cat_Y/.
The behavior on that URL should be: present all children (either only items or only subgroups) with it's previewImage and a hyperlink to example.com/products/cat_1/.../cat_Y/item-X.
The Itemtypes are of course different.
What i did
Classes
I implemented the structure based on the Composite Pattern, so the nodes and the the leafs have a common base class, which - in my example - holds the name of the object. Nodes contain an array of either leafs or nodes, which is filled by a method called addChild().
The implementation is similar to this:
<?php
abstract class Base
{
public $name;
public $previewImage; //path to preview image
public function __construct($name,$preview){
$this->name=$name;
$this->previewImage=$preview;
}
}
class SubGroup extends Base{
public $children= array();
public function getChildrenByPath(Array $subGroupPath){
//example: array("cat_1","cat_1-1",...);
$subChildren=$this->children;
foreach($subGroupPath as $currentPathStep){//follow subGroupPath
foreach($subChildren as $currentChildr){// step through children and compare to path
if($currentChild->uriPattern == $currentPathStep){
$subChildren=$currentChild->getChildren();
break; //end foreach
}
}
}
return $subChildren;
}
public function addChild(Base $new_child){
$this->children[]=$new_child;
}
public function getChildren(){
return $this->children;
}
}
class ProductType01 extends Base{
public property1,property2,property3;
}
?>
Objects / data structure
I set up a few items in a separate php-file to create a dummy structure and try to play around (can later be in a database):
$cat01 = new SubGroup("category 01","preview_cat_01.png");
$cat01->addChild( new ProductType01("Item-01","preview_01.png"));
$cat01->addChild( new ProductType01("Item-02","preview_02.png"));
...
$products = new subGroup("","","products"); //very first node
$products->addChild($cat01);
So the addChild() method adds items to the first category's array.
Presentation
<main>
<?php
$children=$products->getChildrenByPath($uri);
foreach($children as $value){
echo "<div class='previewImage_element'>";
echo "<img src='".$value->previewImage."' style='width:calc(120px *4/3);height:calc(120px*3/4);'>";
echo "<br /><a href='".."'>".$value->name."</a></div>\n";
}
?>
</main>
This is how i list up the previewImages in a table structure, no problem there of course.
This could also be automated as a method as well.
Question
I have no clue if composite (and the way i built it) is the right design pattern and the best approach to reach the desired flexibility and easy to implement structure. At a first glance the Composite Pattern seems to match my hierarchical data but there are is a lot to be made to accomplish an easy implementation.
For example: how to build the hyperlink of the item, since i can't move up the tree structure to collect all levels above?
I am not looking for an "easy" way to set up the classes but to use them easily.
Is the inner array the way to do it or are there more sophisticated ways to point to children / subnodes?
With that kind of structure, how would you guys implement a multi language feature?
And in what way could i keep data and html separated (as much as possible)?
Is this a good way to start? Any hints? any help? Criticism is very welcome!!
You could add a parent variable in your Base class and set it in the addChild. Checking for null will be necessary but it will allow you to move both ways in the tree hierarchy.
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'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!
I am creating an HTML file with DOMDocument, but I have a problem at the time of the search by the getElementsByTagName method. What I found is that as I'm generating the hot, does not recognize the labels that I inserted.
I tried with DOMXPath, but to no avail :S
For now, I've got to do is go through all the children of a node and store in an array, but I need to convert that score DOMNodeList, and in doing
return (DOMNodeList) $ my_array;
generates a syntax error.
My specific question is, how I can do to make a search for tags with the getElementsByTagName method or other alternative I can offer to achieve the task?
Recalling that the DOMDocument I'm generating at the time.
If you need more information, I'll gladly place it in the question.
Sure Jonathan Sampson.
I apologize for the editing of the question the way. I did not quite understand this forum format.
For a better understanding of what I do, I put the inheritance chain.
I have this base class
abstract class ElementoBase {
...
}
And I have this class that inherits from the previous one, with an abstract function insert (insert)
abstract class Elemento extends ElementoBase {
...
public abstract function insertar ( $elemento );
}
Then I have a whole series of classes that represent the HTML tags that inherit from above, ie.
class A extends Elemento {
}
...
Now the code I use to insert the labels in the paper is as follows:
public function insertar ( $elemento ) {
$this->getElemento ()->appendChild ( $elemento->getElemento () );
}
where the function getElemento (), return a DOMElement
Moreover, before inserting the element do some validations that depend on the HTML tag that is to be inserted,
because they all have very specific specifications.
Since I'm generating HTML code at the same time, it is obvious that there is no HTML file.
To your question, the theory tells me to do this:
$myListTags = $this->getElemento ()->getElementsByTagName ( $tag );
but I always returns null, this so I researched it because I'm not loading the HTML file, because if I
$myHtmlFile = $this->getDocumento ()->loadHTMLFile ( $filename );
$myListTags = $myHtmlFile->getElementsByTagName ( $etiqueta );
I do return the list of HTML tags
If you need more information, I'll gladly place it in the question.
I am assuming you have created a valid HTML file with DOMDocument. Your basic problem is to parse or search the HTML doc for a particular tag name.
To search a HTML file the best solution available in PHP is Simple HTML DOM parser.
You can just run the following code and you are done!
$html = file_get_html('url to your html file');
foreach($html->find('tag name') as $element)
{
// perform the action you want to do here.
// example: echo $element->someproperty;
}
$doc = new DOMDocument('1.0', 'iso-8859-1');
$doc->appendChild(
$doc->createElement('Filiberto', 'It works!')
);
$nodeList = $doc->getElementsByTagName('Filiberto');
var_dump($nodeList->item(0)->nodeValue);