php OOP accessing objects directly - php

I am learning and trying to understand things concerning oop so my question might sound very silly.
I want to be able to get to an object directly like this:
$some_urls = new some_urls;
print_r($some_urls->url()->illegal);
Do I have to add the (object) before each array() inside the function or is there a more efficient way ? Thanks and sorry if it is repeated question, I am looking a long time about this.
class some_urls
{
public function url()
{
return (object)array(
'illegal' => (object)array(
'a_path',
'another_path'
),
'legal' => (object)array(
'a_path',
'another_path'
)
);
}
}
EDIT:
I think I found a better alternative based on some ideas by guys here.
You think this is better ?
$some_urls = new some_urls;
print_r($some_urls->get_url('illegal'));
class some_urls
{
public function get_url($data)
{
$url = $this->url();
return $url[$data];
}
protected function url()
{
return array(
'illegal' => array(
'a_path',
'another_path'
),
'legal' => array(
'a_path',
'another_path'
)
);
}
}

You can make use of the stdClass
$object1 = new stdClass();
$object1->a_path = 'hello';

You have many options.
Option 1. Create a standard class, and then add properties as needed
$obj = new stdclass();
$obj->illegal=new stdclass();
Option 2. Just work with an array.
Option 3. Iterate over your array and create an object.
foreach($url as $key=>$value){//Build your object}
Option 4. Use json_decode
$object = json_decode(json_encode($array), FALSE);
Option 5. Add properties to your class.
public variable illegal

Related

In PHP can a StdClass's value be the value of another key?

Not sure how to word this question.
Is it possible to create a new StdClass and have one value be the value of another key(property)?
Without acreating a class
$myObject = (object) [
'base_url' => 'http://www.google.com/',
'route' => $this->base_url . '/some/deeper/path'
];
I know this code is incorrect, but focusing on replacing $this just for getting the concept across.
Is this even possible?
The problem is your array creation is done as a single step, and the source of base_url doesn't exist yet, only as seperate actions, you need to set to get :)
$myObject = new stdClass();
$myObject->base_url = 'http://www.google.com';
$myObject->route = $myObject->base_url . '/some/deeper/path';
Edit: you could use a varaible assignment in the middle of your one liner...
$myObject = (object) [
'base_url' => $base_url = 'http://www.google.com/',
'route' => $base_url . '/some/deeper/path'
];
//unset( $base_url );
Because the action of $base_url = 'http://www.google.com/' happens first before the object creation, thus making a two step process.
You can make a dynamic PHP property using the magic __get method. For example:
<?php
class MyClass {
public $base_url = 'http://www.google.com/';
public function __get($name) {
if ($name == 'route') {
return rtrim($this->base_url,'/') . '/some/deeper/path';
}
throw new \Exception("Property '$name' is not set");
}
}
Do this on stdClass you could subclass it. However stdClass is just like an initial base class anyway.

Using custom functions dynamically in Twig?

I have the following class method for creating a Twig environment object.
public function getView($filename,
array $customFunctions = null,
array $customFunctionArgs = null,
$debug = false) {
$loader = new \Twig_Loader_Filesystem('/App/Views/Templates/Main');
$twig = new \Twig_Environment($loader);
if (isset($customFunctions)) {
foreach ($customFunctions as $customFunction) {
$customFunction['name'] = new \Twig_SimpleFunction($customFunction['name'],
function ($customFunctionArgs) {
return $customFunction['method']($customFunctionArgs);
});
$twig->addFunction($customFunction['name']);
}
}
// Check debugging option
if ($debug == true && !$twig->isDebug()) {
$twig->enableDebug();
$twig->addExtension(new \Twig_Extension_Debug());
} elseif (!$debug && $twig->isDebug()) {
$twig->disableDebug();
}
$template = $twig->load($filename);
return $template;
}
Problem is, I don't understand how to pass values in order to make this work dynamically and keep all the objects in context and scope. For instance, here is how I'm trying to use it but can't pass the variables as a reference I guess?
$customFunctions = ['name' => 'customFunctionName',
'method' => $Class->method($arg)];
$customFunctionArgs = [$arg];
$template = $View->getView('template.html.twig', $customFunctions, $customFunctionArgs, true);
My environment is PHP 5.6 & Twig 1.35.0. I suppose this is not a Twig specific question per se, but more of how to use class objects within other classes/methods.
FĂ©lix Gagnon-Grenier's answer helped me find a solution to this problem. However, I feel the need to post an answer with all the missing pieces to the puzzle for anyone that needs a solution for this.
I believe it will make more sense if I start at the end and explain to the beginning. When creating your array, there are several things to consider.
Any class objects that are needed for the function have to be declared inside a use() with the closure.
Any arguments for the custom function must be declared as a function parameter for the closure. This will allow you to declare them later.
I ended up adding a sub-array with the arguments I needed for each custom function, that way I don't need to iterate over them separately.
$customFunctions = [
[
'name' => 'customFunction',
'method' => function($arg1, $arg2) use($Class) {
return $Class->customFunction($arg1, $arg2);
},
'arguments' =>
[
'arg1', 'arg2'
]
]
];
$template = $View->getView(
'template.html.twig',
true,
$customFunctions
);
echo $View->renderView($template);
Based on this code (reflective of question above), I had to make some notable modifications.
if (isset($customFunctions)) {
foreach ($customFunctions as $index => $customFunction) {
if (isset($customFunctions['arguments'])) {
$arguments = $customFunctions['arguments'];
} else {
$arguments = [];
}
$twigFunction = new \Twig_SimpleFunction(
$customFunction['name'],
function (...$arguments) use ($customFunction) {
return $customFunction['method'](...$arguments);
});
$twig->addFunction($twigFunction);
}
}
You can do this whatever way works for you, but there are important things to consider which I struggled with. Once again, your arguments MUST go into the function parameters. function (...$arguments) use ($customFunction). Your custom function will be passed in the use(). In order to actually pass the arguments in the closure, you must use ... to unpack them (as an array). This applies to PHP 5.6+. It allows the arguments to be dynamically expanded to the correct amount, otherwise you will get missing argument errors.
There are slight flaws in how you construct the custom functions data array and the loop that injects them into the template.
The custom functions should be a three dimensional array
$customFunctions = [
[ // notice the extra level, allowing you to access the name
'name' => 'customFunctionName',
'method' => function() { return 'wat'; }
// you need to pass a callable, not the result of a call
]
];
The scope is not inherited like you seem to think it is, you need to use() variables you intend to access. I personnally would not overwrite the 'name' value of the array, but that's uncanny paranoia of internal side effects, it seems to work in practice.
if (isset($customFunctions)) {
foreach ($customFunctions as $customFunction) {
$customFunction['name'] = new \Twig_SimpleFunction(
$customFunction['name'],
function () use ($customFunctionArgs, $customFunction) {
return $customFunction['method']($customFunctionArgs);
});
$twig->addFunction($customFunction['name']);
}
}
You might need to add looping over $args so that the correct args are sent to the correct function (send $args[0] to $customFunctions[0] etc.).
Note that this prevents you from sending a parameter into your custom function unless you add it in the loop:
function ($templateArg) use ($customFunctionArgs, $customFunction) {
return $customFunction['method']($customFunctionArgs, $templateArg);
}
Here is a gist with tests if you're interested.

Laravel multiple object instantiation

I'm fairly new to Laravel and was wondering what the most efficient way of instantiating and making available multiple objects to my classes was.
An example of what I am doing currently would be, in kind of sudo code:
// MainController.php
<?php
Class MainController extends BaseController {
public function __construct() {
parent::construct();
}
// class code follows
}
// BaseContoller.php
<?php
use classFile;
Use repositoryFile;
use classFile2;
Use repositoryFile2;
..... and lets say 10 more "included" files
Class BaseController extends Controller {
var classFile;
var repositoryFile;
var classFile2;
var repositoryFile2;
..... and 10 more variables;
public function __construct() {
$this->classFile = new classFile;
$this->classFile = new classFile;
$this->repositoryFile = new repositoryFile;
$this->classFile2 = new classFile2;
$this->repositoryFile2 = new repositoryFile2;
..... and 10 more class instantiations
}
// class code follows
}
Hopefully that makes sense.....
Almost all of my classes that inherit BaseController will at some point make use of these objects so loading them all in for each class is I guess the right thing. My main two questions though are:
How expensive, from a pure code point of view, is instantiating multiple objects like this before they are needed. I have included them in the BaseController file because I found myself repeating these included definitions in the numerous files that inherit from BaseController.
From a Laravel point of view is there a better framework way of doing this kind of thing? Originally I had them being injected in through each classes constructor but again they were being repeated (the same definition in multiple files) and were becoming horribly large.
I've followed a lot of the Laracasts tuts as well as various reading material but I've not yet seen how people handle a large volume of objects like I'm trying to use. Or perhaps thats where I'm going "wrong"?
Cheers !
== Heres an example of the call for the homepage of the API I'm working on. It aggregates content from all across the site into a feed for a mobile app:
public function index()
{
$channels = $this->channelRepository->getChannels();
$allChannels = $this->channelRepository->getAllChannels();
$sponsors = $this->sponsorRepository->getSponsors();
$inactiveUserChannels = [];
// use the pattern maker, set the pattern we want to use
$this->patternMaker->setPattern(1);
$channelFeed = [];
if( userIsAuthenticated() )
{
$inactiveUserChannels = $this->userRepository->getUserInactiveChannels( 1 );
}
// Picks
$picks = $this->articleRepository->getArticles( 'picks', 25 );
$ads = $this->sponsorRepository->getWhereNotInCollection( $sponsors, 30 );
$response = $this->patternMaker->make( [ 'articles' => $picks, 'sponsors' => $ads ] );
$picks = $response->articles;
$ads = $response->sponsors;
// Whats on
$channel = 50;
$whatsOn = $this->articleRepository->getArticlesWithEvents(null); // get 20 articles from the whats on channel
$response = $this->patternMaker->make( [ 'articles' => $whatsOn, 'sponsors' => $ads ], "whats-on" );
$whatsOn = $response->articles;
$ads = $response->sponsors;
$channel = $this->channelTransformer->transform( getChannel($channels, $channel) );
$channel['articles'] = $whatsOn;
$channelFeed[] = $channel;
// create a new instance of the channel feed class and pass the required params to it
$this->channelFeed = $this->createChannelFeed( $allChannels, [ 48, 49, 51, 52 ], $ads, $inactiveUserChannels );
if( ! $response = cached("homepage") )
{
$data = [
'channels' => $this->channelTransformer->transformCollection( $channels )
,'adverts' => $this->sponsorTransformer->transformCollection( $sponsors->toArray() )
,'features' => $this->articleTransformer->transformCollection( $this->articleRepository->getArticles( 'featured', 25 ), [ 'showBody' => false] )
,'picks' => $picks
,'channelFeed' => $this->channelFeed->make()
];
cacheIt("homepage", $response, "1 hour");
}
return $this->respondFound(Lang::get('api.homepageFound'), $data);
}
This is early stage and I am refactoring as I go which is where this question of class dependencies has come from.
First, unless your constructor on the MainController have more logic you can just leave out the constructor all together and the BaseController constructor will be called directly.
You could do this:
Class BaseController extends Controller {
protected $classFile;
//repeat for all classes
function classFile() {
if(!$this->classFile) $this->clasFile = new classFile;
return $this->classFile;
}
//repeat for all classes
function useExample() {
$this->classFile()->randomMethod();
}
}
... this way atleast you dont instanciate more than needed.
That beeing said, it sounds like what you really want is Facades, and you could just go:
$channels = ChannelRepository::getChannels();
http://laravel.com/docs/facades

How to pass series of variables in function as array or regardless of their order?

I am sorry, that sounds like a noob question. I am trying to do this, maybe my question is not clear.
I want to be able to pass something like this:
make_thumbnail( array( 'width' => 60, 'height' => 40', 'title' => 'my image' ) );
Now the above line calls the function which already produces the thumbnails I have that no problem, but I want flexibility here. I mean my function has variables ordered like this:
function make_thumbnail($title,$width,$height) {
the code..
echo ...
}
Now you get what I want to do? I want to be able to pass the variables in any order.. they do not have to come in same order title, width, height.. i want to be able to specify the order when I call the function in template as I put in very first line.
I tried to make my question as clear as I can, but really could not find anything about it.
This sort of thing?
function make_thumbnail($myarray) {
$sometitle = $myarray["title"]
$somewidth = $myarray["width"]
$someheight = $myarray["height"]
}
Why not have the array as the function argument? e.g.
function make_thumbnail($argsArray) {
echo $argsArray['width'];
}
You can create variables within your function for each parameter
function make_thumbnail($argsArray) {
$width = $argsArray['width'];
$height = $argsArray['height'];
$title = $argsArray['title'];
// ...plug the rest of your original function here
}
Then your function will behave exactly the same, except you can pass in an array.
What you're asking for is a description of the Reflection syntax of PHP:
function callWithNamedParams( $funcName, array $args = null )
{
if( is_null( $args ) ) return $funcName();
$f = new ReflectionFunction($funcName);
$input = array();
foreach( $f->getParameters() as $param )
{
array_push( $input, #$args[ $param->getName() ] );
}
return call_user_func_array( $funcName, $input );
}
Use:
function myFunc( $foo, $bar )
{
echo "foo = $foo; Bar = $bar";
}
callWithNamedParams( "myFunc", array( "bar"=>1, "foo"=>2 ) );
You should get foo = 2; Bar = 1 as an output.
you need to define your logic to take any parameter as the one you want it to be. Array is the best thing you can use. But changing the parameters changes the signatures.
you are kinda implementing polymorphism but in a wrong way..

How to use call_user_func_array with an object with a __construct method in PHP

How could I use call_func_array to create a new object with a __construct method (with some not optional arguments).
Here's the code:
$urls = array(
'view' => array(
'view/(\d+)',
array('controller' => 'test', 'action' => 'view'),
array(1 => 'id'),
),
);
foreach ($urls as $name => $args) {
$route = call_user_func_array(Zend_Controller_Router_Route_Regex, $args);
$router->addRoute($name, $route);
}
$ref = new ReflectionClass('Zend_Whatever');
foreach ($urls as $name => $args) {
$route = $ref->newInstanceArgs($args);
$router->addRoute($name, $route);
}
The signature for the constructor is
($route, [ $defaults = array()], [ $map = array()], [ $reverse = null])
Thus, I would have an array with the empty arguments, and merge that with the actual arguments for each router in your loop. If you want to make it simple to specify only the last option, then use string keys in your config array, and in your default array.
$blank_opts = array('', array(), array(), null); //default options and blank route
foreach ($urls as $name => $args) {
//replaces default options with options from the array, if set
$opts = array_replace($blank_opts, $args);
//create the new router
$route = new Zend_Controller_Router_Route_Regex($opts[0], $opts[1], $opts[2], $opts[3]);
$router->addRoute($name, $route);
}
For one, you seem to have a syntax error (as Zend_Controller_Router_Route_Regex needs to be a string.
Thus, one could think this would work:
$route = call_user_func_array(array('Zend_Controller_Router_Route_Regex', '__construct'), $args);
However, as far as I know, the first element of the array (the first parameter) can either be a string when calling a static class method or an instance of your class for any method. Neither is the case here. Thus, I would just do it this way:
$route = new Zend_Controller_Router_Route_Regex;
call_user_func_array(array('Zend_Controller_Router_Route_Regex', 'setOptions'), $args);
You might have to use array($args) instead of $args depending on the type of that variable.
EDIT No, I am wrong. There is no setOptions() function for the routes. Let me check something...
EDIT2 A rather ugly hack I found in the PHP manual regarding call_user_func_array() is the following:
$route = new Zend_Controller_Router_Route_Regex;
call_user_func_array(array($route, '__construct'), $args);
However, I did not test this and it might not work (if it can work at all) depending on the implementation of the constructor (Does it accept no parameters being passed to it? Does it just setup the class or are other things done, too?). Please tell me if it worked...

Categories