Zend View Helper Question - php

I have finally got a zend view helper working using this in my helper file:
class MY_View_Helper_Table extends Zend_View_Helper_Abstract
{
private $table_data="",$table_head="";
public function Table($data=''){
return "hello";
}
}
and this in my view:
print $this->Table();
This just prints out the returned value of the constructor, I think. How do I go about calling other methods of the class? I dont really know how to refer to the instanced object to access its methods.

I have managed to sort of do it using
method chaining, in Table I return
$this; but there must be a better and
normal way of doing it.
Actually no. Thats typically how you do it. Because of how view helpers work, if you need access to other methods on the helper then you either always return $this from your table method or you detect what to invoke by the parameter signature passed to it. For eaxmple:
public function table($options = null)
{
if(null === $options){
return $this;
}
if(is_array($options)){
return $this->tableFromArray($options);
}
// etc..
}
You can also get the helper instance with $this->getHelper('name') and then chain to the method you want... but IMO thats more confusing than doing parameter detection of just treating the default method as a getter.

Related

PHP call class variable / property with stored closure

So I am making a Magento module in PHP. I want my logging to be consistent across all classes. My idea was to define a method in the helper class and call it. However being the pre-optimizer that I am, I figure making multiple calls to a class via the Mage::Helper() method to be more expensive than it needs to be, especially since most of my logging in singleton models anyways. So my now my idea is to use closures, define my method in the helper, make one call to the helper and register the method in a class variable.
class Comp_Mod_Helper_Data extends Mage_Core_Helper_Abstract {
public function getLogger() {
return function ($obj, $type= Zend_Log::DEBUG) {
Mage::log($obj, $logType, 'comp-mod.log', true);
};
}
}
Use:
class Comp_Mod__IndexController extends age_Core_Controller_Front_Action {
private $_log;
protected function _construct() {
$this->_log = Mage::Helper('mod')->getLogger();
}
}
However while it works ... it is not great to use. I'm either stuck doing:
$log = $this->_log;
$log('hello world');
// one awkward liner
($this->_log)('hello world');
While neat that it works is not readable nor standard, i.e. confusing!. The error that it get when using $this->_log('hello world'); is that the method does not exist. I assume because PHP is looking for a method call when using the syntax $this->method();
I do understand that A) I could just suck it up and use Mage::Helper everywhere, and B) that I could store the helper object in a variable and call like $this->helper->log(), and C) that static variables work, see PHP closure as static class variable
So, is there a way to get a non-static class variable to call the closure instead of looking for a non-existing method?
You could make use of the __call magic method:
class Comp_Mod__IndexController extends age_Core_Controller_Front_Action {
public function __call($method, array $args)
{
switch ($method)
{
case '_log':
return call_user_func_array(Mage::Helper('mod')->getLogger(), $args);
}
return null;
}
}
Then use it like you wanted to:
$this->_log('string to log');

PHP one method for both static and instanciated scope

While creating a PHP helper method, I am trying to find an elegant way to have the same method available for both static calls and instanciated calls, while still being able to access the instance when called from the instance call.
Consider getModelName() method within an Entity class:
public static function getModelName($entity) {
if (is_string($entity)) {
$entity = ///query to return an entity object;
return !empty($entity) ? $entity->model : false;
}
if (is_a($entity, "\namespace\path\Entity")) {
return $entity->model;
}
return false;
}
Since different classes across the application use this method, and some of them already have an instance of $entity, I want to be able to call it directly so I don't need to pass an instance of $entity as an argument every time:
$entity->getModelName();
The method itself need to be able to do:
public static function getModelName($entity = null) {
if (is_string($entity)) {
$entity = ///query to return an entity object;
return !empty($entity) ? $entity->model : false;
}
if (empty($entity)) {
//this is where it fails of course since $this is not available
//in a static method
return $this->model;
}
return false;
}
This solution obviously fails since $this is not available within a static method.
Any ideas how to solve this predicament without having 2 different methods?
Thanks for the help!
UPDATE
I am using Laravel so $entity is a database ORM model (Eloquent). I didn't think it's relevant originally because this is a general question about PHP method scopes. After reading the comments I see it's important to mention. Instanciaing a new $entity is only an option if I have to since it will query the database.
Since there's no way to access $this inside static methods, you should look at this problem from a different angle.
Simply put, $this is an instance of the current class, therefore you need to instantiate it and then access its properties or methods. So you can handle it like this:
if (empty($entity)) {
$instance = new self();
return $instance->model;
}

Why I'm getting 'Non-static method should not be called statically' when invoking a method in a Eloquent model?

Im trying to load my model in my controller and tried this:
return Post::getAll();
got the error Non-static method Post::getAll() should not be called statically, assuming $this from incompatible context
The function in the model looks like this:
public function getAll()
{
return $posts = $this->all()->take(2)->get();
}
What's the correct way to load the model in a controller and then return it's contents?
You defined your method as non-static and you are trying to invoke it as static. That said...
1.if you want to invoke a static method, you should use the :: and define your method as static.
// Defining a static method in a Foo class.
public static function getAll() { /* code */ }
// Invoking that static method
Foo::getAll();
2.otherwise, if you want to invoke an instance method you should instance your class, use ->.
// Defining a non-static method in a Foo class.
public function getAll() { /* code */ }
// Invoking that non-static method.
$foo = new Foo();
$foo->getAll();
Note: In Laravel, almost all Eloquent methods return an instance of your model, allowing you to chain methods as shown below:
$foos = Foo::all()->take(10)->get();
In that code we are statically calling the all method via Facade. After that, all other methods are being called as instance methods.
Why not try adding Scope? Scope is a very good feature of Eloquent.
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
$users = User::popular()->women()->orderBy('created_at')->get();
Eloquent #scopes in Laravel Docs
TL;DR. You can get around this by expressing your queries as MyModel::query()->find(10); instead of MyModel::find(10);.
To the best of my knowledge, starting PhpStorm 2017.2 code inspection fails for methods such as MyModel::where(), MyModel::find(), etc (check this thread), and this could get quite annoying.
One (elegant) way to get around this is to explicitly call ::query() wherever it makes sense to. This will let you benefit from free auto-completion and a nice formatting/indentating for your queries.
Examples
BAD
Snippet where inspection complains about static method calls
// static call complaint
$myModel = MyModel::find(10);
// another poorly formatted query with code inspection complaints
$myFilteredModels = MyModel::where('is_foo', true)
->where('is_bar', false)
->get();
GOOD
Well formatted code with no complaints
// no complaint
$myModel = MyModel::query()->find(10);
// a nicely formatted and indented query with no complaints
$myFilteredModels = MyModel::query()
->where('is_foo', true)
->where('is_bar', false)
->get();
Just in case this helps someone, I was getting this error because I completely missed the stated fact that the scope prefix must not be used when calling a local scope. So if you defined a local scope in your model like this:
public function scopeRecentFirst($query)
{
return $query->orderBy('updated_at', 'desc');
}
You should call it like:
$CurrentUsers = \App\Models\Users::recentFirst()->get();
Note that the prefix scope is not present in the call.
Solution to the original question
You called a non-static method statically. To make a public function static in the model, would look like this:
public static function {
}
In General:
Post::get()
In this particular instance:
Post::take(2)->get()
One thing to be careful of, when defining relationships and scope, that I had an issue with that caused a 'non-static method should not be called statically' error is when they are named the same, for example:
public function category(){
return $this->belongsTo('App\Category');
}
public function scopeCategory(){
return $query->where('category', 1);
}
When I do the following, I get the non-static error:
Event::category()->get();
The issue, is that Laravel is using my relationship method called category, rather than my category scope (scopeCategory). This can be resolved by renaming the scope or the relationship. I chose to rename the relationship:
public function cat(){
return $this->belongsTo('App\Category', 'category_id');
}
Please observe that I defined the foreign key (category_id) because otherwise Laravel would have looked for cat_id instead, and it wouldn't have found it, as I had defined it as category_id in the database.
You can give like this
public static function getAll()
{
return $posts = $this->all()->take(2)->get();
}
And when you call statically inside your controller function also..
I've literally just arrived at the answer in my case.
I'm creating a system that has implemented a create method, so I was getting this actual error because I was accessing the overridden version not the one from Eloquent.
Hope that help?
Check if you do not have declared the method getAll() in the model. That causes the controller to think that you are calling a non-static method.
For use the syntax like return Post::getAll(); you should have a magic function __callStatic in your class where handle all static calls:
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}

PHP - Can I pass a function name as a function argument?

I have two classes that I use to access two different tables in my db. They both have a similar constructor that looks like that:
function __construct($db) {
$this->db = $db;
$userDAO = DAO_DBrecord::createUserDAO($this->db);
$this->userDAO = $userDAO;
}
The other class has the same constructor except that it uses createOtherTableDAO($this->db).
I am planning on having a couple other such classes, and it would be convenient if I could have them all inherit the same constructor, and pass createAppropriateTableDAO as an argument.
To clarify, in the first case above, createUserDAO($this->db) is a static function that calls a constructor in my DAO class. The function in the DAO looks as follows:
public static function createUserDAO($db) {
return new DAO_DBrecord($db, 'users');
}
I use this method to make sure the user model can only call a DAO on the users table.
I'm somewhat of a beginner, and I don't think I have ever seen anything like what I want.
Move the code to create the DAOs into a Factory and then inject the DAOs instead of hard coupling them into whatever these classes are supposed to represent. Or rather create the various Table Data Gateways ("classes that I use to access two different tables") as a whole in the Factory, e.g.
class TableDataGatewayFactory
…
public function create($gatewayName)
{
switch ($gatewayName) {
case 'user':
return new TableDataGateway(new UserDao($this->db)));
break;
default:
throw new Exception('No Gateway for $gatewayName');
}
}
}
As for $this->db, either pass that into the Factory via the ctor or move the creation into the Factory as well. It's somewhat doubled responsibility, but tolerable given that this Factory revolved around creating Database related collaborator graphs.
Apart from that: yes, call_user_func(array('ClassName', 'methodName')) would work. See the manual for
http://php.net/call_user_func and
http://php.net/manual/en/language.pseudo-types.php#language.types.callback
To answer your question first: No, you can't (without resorting to evilCode) pass a function name as a parameter.
But: What you want to archive is a poster-child-issue for an object oriented approach using inheritance.
You'd need a base-class:
class BaseClass
{
function __construct($db) {
$this->db = db;
}
}
and your implementations :
class MyClass extends BaseClass
{
function __construct($db) {
parent::__contruct($db);
$this->userDAO = DAO_DBrecord::createUserDAO($this->db);
}
}
Just for the record: the evilCode would have been
a) you could encapsulate your function in a create_function that can be used as an argument.
b) you could pass the function name as a string to your function and then pass it to eval in the receiving function.
But remember: When eval or create_function looks like the answer you're probably asking the wrong questions!
See: related question
There are several methods which you can use if you feel it necessary to pass the function name or indeed the function itself as a parameter of a function.
call_user_func($function,$args);
call_user_func is one of Php's native functions for invoking methods or functions which takes a function name and optional arguments parameter.
The functionality of call_user_func (when not pertaining to object methods) can be replicated without the using call_user_func using a variable with the string literal of the function name. For example:
function some_func()
{
echo "I'm a function!";
}
$function = "some_func";
$function(); /*Output: I'm a function!*/
And if you're feeling adventurous you can go a bit further and pass a closure / anonymous function as instead of the function name. For example:
$function = function()
{
echo "I'm another function!";
}
$function(); /*Output: I'm another function*/
You can achieve such behavior by using:
call_user_func
eval any literal

Zend Framework: Pass by reference to view helper not working

Here is a simple view helper (notice the pass-by-reference argument):
class Zend_View_Helper_MyViewHelper extends Zend_View_Helper_Abstract
{
public function MyViewHelper(&$array)
{
unset($array['someExistingKey']);
}
}
This does not work in the view. $array['someExistingKey'] is still set (except within the immediate context of the method). Zend must be doing something to prevent the array from being passed in by reference. Any ideas on a solution?
When you call $this->MyViewHelper($array) from your templates you are not actually calling the helper class directly, Zend_View is instantiating the class and calling it for you. So I think you might have trouble getting this working. Your best bet is probably to use Zend_Registry, or refactor to take a different approach not requiring a global.
I just thought of a workaround. You just have to call the helper manually, instead of letting ZF call it through call_user_func_array.
Ref.php
class Zend_View_Helper_Ref extends Zend_View_Helper_Abstract
{
public function removeFromRef(&$ref)
{
// change your var value here
unset($ref['key']);
}
/**
* ZF calls this for us, but we'll call what we want, so you can skip this.
*/
// public function ref()
// {}
}
As you can see, you can skip the convention of having to name your main method as the filename, but I still recommend it.
Now, you can pass references in views/controllers:
// in view:
$this->getHelper('Ref')->removeFromRef($someVar2Change);
// in controller
$this->view->getHelper('Ref')->removeFromRef($someVar2Change);
Basically, this is what $this->ref() does: gets the helper, then calls call_user_func_array.
Some people may have problems using $this->getHelper('Ref')->ref() instead of $this->ref() though, but it works.

Categories