Modify Stack in the Zend HeadScript View Helper - php

I am trying to attack this problem from a completely different angle, because it doesn't look like I can achieve my goal that way.
I want to loop over the item stack in the HeadScript View Helper, and make modifications to it. The documentation for this and some of the other view helpers makes this statement:
HeadScript overrides each of append(),
offsetSet(), prepend(), and set() to
enforce usage of the special methods
as listed above. Internally, it stores
each item as a stdClass token, which
it later serializes using the
itemToString() method. This allows
you to perform checks on the items in
the stack, and optionally modify these
items by simply modifying the object
returned.
So, where is this "object returned"? I am missing a piece of the puzzle here.
Thanks for your help!

In the toString() method of Zend_View_Helper_HeadScript I noticed a foreach() loop on $this, so I tried that and it worked. Here's a HeadScript extension I wrote that illustrates the solution:
class My_View_Helper_HeadScript extends Zend_View_Helper_HeadScript
{
public function toString($indent = null)
{
$files = array();
foreach ($this as $key => $item) {
if (!empty($item->attributes)
&& array_key_exists('src', $item->attributes)
&& ('scripts' == substr($item->attributes['src'], 1, 7))) {
$files[] = $item->attributes['src'];
unset($this[$key]);
}
}
if (0 < count($files)) {
$this->prependFile('/combo.php?type=scripts&files=' . implode(',', $files));
}
return parent::toString($indent);
}
}
In Bootstrap.php the following lines to point to my helpers:
$this->bootstrap('view');
$view = $this->getResource('view');
$view->addHelperPath('My/View/Helper', 'My_View_Helper');
In my layout, I have this line:
<?php echo $this->headScript(); ?>
If my solution is unclear in any way, let me know and I'll update it to clarify.

Related

PHP include external method and class

I'm new to PHP and I have an issue I can't seem to fix or find a solution to.
I'm trying to create a helper function that will return an 'object' filled with information pulled from an XML file. This helper function, named functions.php contains a getter method which returns a 'class' object filled with data from an SVN log.xml file.
Whenever I try to import this file using include 'functions.php'; none of the code after that line runs the calling function's page is blank.
What am I doing wrong?
Here is what the functions.php helper method and class declaration looks like:
<?php
$list_xml=simplexml_load_file("svn_list.xml");
$log_xml=simplexml_load_file("svn_log.xml");
class Entry{
var $revision;
var $date;
}
function getEntry($date){
$ret = new Entry;
foreach ($log_xml->logentry as $logentry){
if ($logentry->date == $date){
$ret->date = $logentry->date;
$ret->author = $logentry->author;
}
}
return $ret;
}
I'm not sure what the point of having a separate helper function from the class is, personally I'd combine the two. Something like this
other-file.php
require './Entry.php';
$oLogEntry = Entry::create($date, 'svn_log.xml');
echo $oLogEntry->date;
echo $oLogEntry->revision;
Entry.php
class Entry
{
public $revision;
public $date;
public $author;
public static function create($date, $file) {
$ret = new Entry;
$xml = simplexml_load_file($file);
foreach($xml->logentry as $logentry) {
if($logentry->date == $date) {
$ret->date = $logentry->date;
$ret->author = $logentry->author;
$ret->revision = $logentry->revision;
}
}
return $ret;
}
}
EDIT
In light of the fact OP is new to PHP, I'll revise my suggestion completely. How about ditching the class altogether here? There's hardly any reason to use a class I can see at this point; let's take a look at using an array instead.
I might still move the simplexml_load_file into the helper function though. Would need to see other operations to merit keeping it broken out.
entry-helper.php
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
$entry = array();
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
$entry['date'] = $logentry->date;
$entry['author'] = $logentry->author;
$entry['revision'] = $logentry->revision;
}
}
return $entry;
}
other-file.php
require './entry.php';
$aLogEntry = Entry::create($date, 'svn_log.xml');
echo $aLogEntry['date'];
echo $aLogEntry['revision'];
EDIT
One final thought.. Since you're seemingly searching for a point of interest in the log, then copying out portions of that node, why not just search for the match and return that node? Here's what I mean (a return of false indicates there was no log from that date)
function getEntry($date, $file) {
$log_xml = simplexml_load_file($file);
foreach($log_xml->logentry as $logentry) {
if($logentry->date == $date) {
return $logentry;
return false;
}
Also, what happens if you have multiple log entries from the same date? This will only return a single entry for a given date.
I would suggest using XPATH. There you can throw a single, concise XPATH expression at this log XML and get back an array of objects for all the entries from a given date. What you're working on is a good starting point, but once you have the basics, I'd move to XPATH for a clean final solution.

FOSRestBundle adds 'S' in get URL

// MySomethingController.php
// look no s
public function getSomethingAction($args)
{
...
}
// routing.yml
my_something:
type: rest
resource: Blah\Bundle\BlahBundle\Controller\MySomethingController
running:
php app/console router:debug
Output:
[router] Current routes
Name Method Pattern
get_something GET /somethings/{args}.{_format}
Why is the route 'somethings' ( plural with an 's' ) instead of 'something'?
is this a setting I have somewhere? or is this expected?
after digging in the code:
https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/Routing/Loader/Reader/RestActionReader.php
Here it is:
private function generateUrlParts(array $resources, array $arguments)
{
$urlParts = array();
foreach ($resources as $i => $resource) {
// if we already added all parent routes paths to URL & we have
// prefix - add it
if (!empty($this->routePrefix) && $i === count($this->parents)) {
$urlParts[] = $this->routePrefix;
}
// if we have argument for current resource, then it's object.
// otherwise - it's collection
if (isset($arguments[$i])) {
if (null !== $resource) {
$urlParts[] =
strtolower(Pluralization::pluralize($resource))
.'/{'.$arguments[$i]->getName().'}';
} else {
$urlParts[] = '{'.$arguments[$i]->getName().'}';
}
} elseif (null !== $resource) {
$urlParts[] = strtolower($resource);
}
}
return $urlParts;
}
I've opened an issue:
https://github.com/FriendsOfSymfony/FOSRestBundle/issues/247
in hopes that this would become optional
This is an update answer based on the ticket opened by #phillpafford.
Ismith77 commented on the ticket and I thought explained very well why:
Without the pluralization we wouldn't know the relationship between
the methods which is going to be important for example when
implementing #52. furthermore its a key idea of REST that we have the
GET for a single element in a collection be in a "subdir" of the
collection itself.
So if you do "proper" REST then /member/{id}.{_format} would be oddly
named but it would be actually wrong if your collection itself
wouldn't then also reside under /member{.format}.
The gist of all of this is .. the solution as is isn't so much about
convenience than it is about enforcing people actually following REST
principles.
PS: However I would like to point out that when you have a word like "data" which is plural on it's own this is a bit annoying...

Paginator zend framework warning

Warning: No view partial provided and
no default set in
/Applications/MAMP/htdocs/getv/library/Zend/Paginator.php
on line 465
This is the warning message that I get when loading the paginator; can someone give me a solution or tips where my problem could be?
public function getPaginator() {
if ($this->view === null) {
$this->view = $this->getActionController()->view;
}
$db = Zend_Db_Table::getDefaultAdapter();
/* #var $searcher ZendX_Searcher_Abstract */
foreach ($this->searchers as $searcher) {
$searcher->setRequest($this->getRequest())
->setView($this->view)
->setSelect($this->select)
->perform();
}
$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_DbSelect($this->select));
$paginator->setCurrentPageNumber($this->getRequest()->getParam('page', 1));
$paginator->setPageRange(7);
if ($this->perPage > 0) {
$paginator->setItemCountPerPage($this->perPage);
} else {
$paginator->setItemCountPerPage(PHP_INT_MAX);
}
return $paginator;
}
Hey I was getting the same problem. I am using zend pagination view helper. The solution I got is little strange. In the view(.phtml) file I was checking like
if(isset($this->records) && $this->records!='')
but later than i change this to
if(isset($this->records) && sizeof($this->records) > 0)
and my problem solved. Hope, it may be helpful to you and others.
Correct solution is IMO what RobertPitt proposes:
Zend_View_Helper_PaginationControl::setDefaultViewPartial ('paginator.phtml' );
This error is not triggered from your controller but rather, from your view.
Show the script where you call <?php echo $this->paginationControl(...) ?>
For reference, you need to supply at least two things to the PaginationControl view helper:
A Zend_Paginator object. Supplied as the first argument to the helper or by setting it as the paginator property of your view.
A partial view path. Supplied as the third argument to the view helper (after scrollingStyle) or via the static method RobertPitt mentioned in his comment.
In my case I don't give to view an empty paginator object but an empty string, this to avoid an initial not filtered search.
I solved in this way in my view.phtml:
if ( is_a($this->paginator,'Zend\Paginator\Paginator') ) {
// paginator print
}
I hope this help.

Autocreate object when property is called

Im wondering if there is a way to autocreate object if a property is called. An example:
<?php
echo $myObj->myProperty
?>
This code will of course fail because i did not initiate $myObj before reading the property.
What im looking for is a way to automaticly initiate $myObj based on "myObj".
Something like:
<?php
class myObj {
public myProperty = 'BlaBla';
}
echo $myObj->myProperty; //outputs BlaBla instead of failing
?>
I know about __autoload($classname) but that only works of initiating classcode with i.e. an include(), so that is not what im after.
You can use magic methods to automate stuff like that...
http://www.php.net/manual/en/language.oop5.magic.php
Just to close this question, this is what i ended up doing:
preg_match_all("/\\\$(.*?)->/si", $code, $matches);
I loop trough the code i get from database looking for any references to objects like
$xxxx->
Then i loop trough the references and create the objects
foreach($matches[1] as $key=>$value) {
$$value = Connector::loadConnector($value);
}
Where the "loadConnector is:
public function loadConnector($connector, $params = NULL) {
require_once $connector. ".php";
$c_name = $connector;
return new $c_name($params);
}
This is of course based on my file structure and it also needs some errorhandling, but so far it looks like it solves my problem :)
BR/Sune

A Generic, catch-all action in Zend Framework... can it be done?

This situation arises from someone wanting to create their own "pages" in their web site without having to get into creating the corresponding actions.
So say they have a URL like mysite.com/index/books... they want to be able to create mysite.com/index/booksmore or mysite.com/index/pancakes but not have to create any actions in the index controller. They (a non-technical person who can do simple html) basically want to create a simple, static page without having to use an action.
Like there would be some generic action in the index controller that handles requests for a non-existent action. How do you do this or is it even possible?
edit: One problem with using __call is the lack of a view file. The lack of an action becomes moot but now you have to deal with the missing view file. The framework will throw an exception if it cannot find one (though if there were a way to get it to redirect to a 404 on a missing view file __call would be doable.)
Using the magic __call method works fine, all you have to do is check if the view file exists and throw the right exception (or do enything else) if not.
public function __call($methodName, $params)
{
// An action method is called
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, -6);
// We want to render scripts in the index directory, right?
$script = 'index/' . $action . '.' . $this->viewSuffix;
// Script file does not exist, throw exception that will render /error/error.phtml in 404 context
if (false === $this->view->getScriptPath($script)) {
require_once 'Zend/Controller/Action/Exception.php';
throw new Zend_Controller_Action_Exception(
sprintf('Page "%s" does not exist.', $action), 404);
}
$this->renderScript($script);
}
// no action is called? Let the parent __call handle things.
else {
parent::__call($methodName, $params);
}
}
You have to play with the router
http://framework.zend.com/manual/en/zend.controller.router.html
I think you can specify a wildcard to catch every action on a specific module (the default one to reduce the url) and define an action that will take care of render the view according to the url (or even action called)
new Zend_Controller_Router_Route('index/*',
array('controller' => 'index', 'action' => 'custom', 'module'=>'index')
in you customAction function just retrieve the params and display the right block.
I haven't tried so you might have to hack the code a little bit
If you want to use gabriel1836's _call() method you should be able to disable the layout and view and then render whatever you want.
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
I needed to have existing module/controller/actions working as normal in a Zend Framework app, but then have a catchall route that sent anything unknown to a PageController that could pick user specified urls out of a database table and display the page. I didn't want to have a controller name in front of the user specified urls. I wanted /my/custom/url not /page/my/custom/url to go via the PageController. So none of the above solutions worked for me.
I ended up extending Zend_Controller_Router_Route_Module: using almost all the default behaviour, and just tweaking the controller name a little so if the controller file exists, we route to it as normal. If it does not exist then the url must be a weird custom one, so it gets sent to the PageController with the whole url intact as a parameter.
class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
{
private $_catchallController = 'page';
private $_catchallAction = 'index';
private $_paramName = 'name';
//-------------------------------------------------------------------------
/*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
with the following changes:
- if the path includes a valid module, then use it
- if the path includes a valid controller (file_exists) then use that
- otherwise use the catchall
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
$file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php';
if (file_exists( $file ))
{
$values[$this->_controllerKey] = array_shift($path);
}
else
{
$values[$this->_controllerKey] = $this->_catchallController;
$values[$this->_actionKey] = $this->_catchallAction;
$params[$this->_paramName] = join( self::URI_DELIMITER, $path );
$path = array();
}
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
}
So my MemberController will work fine as /member/login, /member/preferences etc, and other controllers can be added at will. The ErrorController is still needed: it catches invalid actions on existing controllers.
I implemented a catch-all by overriding the dispatch method and handling the exception that is thrown when the action is not found:
public function dispatch($action)
{
try {
parent::dispatch($action);
}
catch (Zend_Controller_Action_Exception $e) {
$uristub = $this->getRequest()->getActionName();
$this->getRequest()->setActionName('index');
$this->getRequest()->setParam('uristub', $uristub);
parent::dispatch('indexAction');
}
}
You could use the magic __call() function. For example:
public function __call($name, $arguments)
{
// Render Simple HTML View
}
stunti's suggestion was the way I went with this. My particular solution is as follows (this uses indexAction() of whichever controller you specify. In my case every action was using indexAction and pulling content from a database based on the url):
Get an instance of the router (everything is in your bootstrap file, btw):
$router = $frontController->getRouter();
Create the custom route:
$router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));
Pass the new route to the front controller:
$frontController->setRouter($router);
I did not go with gabriel's __call method (which does work for missing methods as long as you don't need a view file) because that still throws an error about the missing corresponding view file.
For future reference, building on gabriel1836 & ejunker's thoughts, I dug up an option that gets more to the point (and upholds the MVC paradigm). Besides, it makes more sense to read "use specialized view" than "don't use any view".
// 1. Catch & process overloaded actions.
public function __call($name, $arguments)
{
// 2. Provide an appropriate renderer.
$this->_helper->viewRenderer->setRender('overload');
// 3. Bonus: give your view script a clue about what "action" was requested.
$this->view->action = $this->getFrontController()->getRequest()->getActionName();
}
#Steve as above - your solution sounds ideal for me but I am unsure how you implmeented it in the bootstrap?

Categories