PHP OOP nested methods? - php

I know that you can use nested functions in PHP.
Here is the question:
I have this code:
class ClassName
{
private $data;
function __construct()
{
}
public function myFunction()
{
if($condition1 != NULL)
{
if(!empty($condition2) && $this->data->condition3 == NULL)
{
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
$this->doBLAH();
}
elseif(empty($checkoutItem->rebillCheckoutItemId))
{
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
$this->doSomethingElse();
}
}
}
}
As you can see, this part is redundant:
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
So get rid off redundancy i could create a new method:
private function doSomething($condition2)
{
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
}
But i don't want to. It might really mess my code up.
Just wondering if its possible to do something like:
public function myFunction()
{
if($condition1 != NULL)
{
if(!empty($condition2) && $this->data->condition3 == NULL)
{
$this->doSomething($condition2);
$this->doBLAH();
}
elseif(empty($checkoutItem->rebillCheckoutItemId))
{
$this->doSomething($condition2);
$this->doSomethingElse();
}
}
function doSomething($condition2)
{
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
}
}
I tried it but it throws a Fatal error: Call to undefined function doSomething(). Are there any tricks? Am i doing something wrong?
UPDATE
I'm working in YII framework and it gives me an exception: BlahWidget and its behaviors do not have a method or closure named "doSomething"

You can define an anonymous function in your local scope instead. Otherwise, the function will still be defined in the global scope or namespace.
This should work from PHP 5.4 because $this is used within the closure. In 5.3, you will need to do additional gymnastics. See below.
public function myFunction()
{
$do = function ($condition2) {
$this->do1($condition2);
$this->do3($condition2);
$this->do3($condition2);
};
if($condition1 != NULL)
{
if(!empty($condition2) && $this->data->condition3 == NULL)
{
$do($condition2);
$this->doBLAH();
}
elseif(empty($checkoutItem->rebillCheckoutItemId))
{
$do($condition2);
$this->doSomethingElse();
}
}
}
And now the 5.3 version. Note that the functions called need to be public.
public function myFunction()
{
$that = $this;
$do = function ($condition2) use ($that) {
$that->do1($condition2);
$that->do3($condition2);
$that->do3($condition2);
};
if($condition1 != NULL)
{
if(!empty($condition2) && $this->data->condition3 == NULL)
{
$do($condition2);
$this->doBLAH();
}
elseif(empty($checkoutItem->rebillCheckoutItemId))
{
$do($condition2);
$this->doSomethingElse();
}
}
}
In an example like this, for 5.3, I would rather create a private method on the class.

First of all, you can't have a function called do(). It is reserved.
So let's change your do() to do_somthing(). Then you need to call it with $this->do_something() wherever you want to make use of it.

Related

simplify nested if statements

I'm implementing a search functionality and based on the query parameter i use a different class to search.
class Search {
public function getResults()
{
if (request('type') == 'thread') {
$results = app(SearchThreads::class)->query();
} elseif (request('type') == 'profile_post') {
$results = app(SearchProfilePosts::class)->query();
} elseif (request()->missing('type')) {
$results = app(SearchAllPosts::class)->query();
}
}
Now when i want to search threads i have the following code.
class SearchThreads{
public function query()
{
$searchQuery = request('q');
$onlyTitle = request()->boolean('only_title');
if (isset($searchQuery)) {
if ($onlyTitle) {
$query = Thread::search($searchQuery);
} else {
$query = Threads::search($searchQuery);
}
} else {
if ($onlyTitle) {
$query = Activity::ofThreads();
} else {
$query = Activity::ofThreadsAndReplies();
}
}
}
}
To explain the code.
If the user enters a search word ( $searchQuery) then use Algolia to search, otherwise make a database query directly.
If the user enters a search word
Use the Thread index if the user has checked the onlyTitle checkbox
Use the Threads index if the user hasn't checked the onlyTitle checkbox
If the user doesn't enter a search word
Get all the threads if the user has checked the onlyTitle checkbox
Get all the threads and replies if the user hasn't checked the onlyTitle checkbox
Is there a pattern to simplify the nested if statements or should i just create a separate class for the cases where
a user has entered a search word
a user hasn't entered a search word
And inside each of those classes to check if the user has checked the onlyTitle checkbox
I would refactor this code to this:
Leave the request parameter to unify the search methods in an interface.
interface SearchInterface
{
public function search(\Illuminate\Http\Request $request);
}
class Search {
protected $strategy;
public function __construct($search)
{
$this->strategy = $search;
}
public function getResults(\Illuminate\Http\Request $request)
{
return $this->strategy->search($request);
}
}
class SearchFactory
{
private \Illuminate\Contracts\Container\Container $container;
public function __construct(\Illuminate\Contracts\Container\Container $container)
{
$this->container = $container;
}
public function algoliaFromRequest(\Illuminate\Http\Request $request): Search
{
$type = $request['type'];
$onlyTitle = $request->boolean('only_title');
if ($type === 'thread' && !$onlyTitle) {
return $this->container->get(Threads::class);
}
if ($type === 'profile_post' && !$onlyTitle) {
return $this->container->get(ProfilePosts::class);
}
if (empty($type) && !$onlyTitle) {
return $this->container->get(AllPosts::class);
}
if ($onlyTitle) {
return $this->container->get(Thread::class);
}
throw new UnexpectedValueException();
}
public function fromRequest(\Illuminate\Http\Request $request): Search
{
if ($request->missing('q')) {
return $this->databaseFromRequest($request);
}
return $this->algoliaFromRequest($request);
}
public function databaseFromRequest(\Illuminate\Http\Request $request): Search
{
$type = $request['type'];
$onlyTitle = $request->boolean('only_title');
if ($type === 'thread' && !$onlyTitle) {
return $this->container->get(DatabaseSearchThreads::class);
}
if ($type === 'profile_post' && !$onlyTitle) {
return $this->container->get(DatabaseSearchProfilePosts::class);
}
if ($type === 'thread' && $onlyTitle) {
return $this->container->get(DatabaseSearchThread::class);
}
if ($request->missing('type')) {
return $this->container->get(DatabaseSearchAllPosts::class);
}
throw new InvalidArgumentException();
}
}
final class SearchController
{
private SearchFactory $factory;
public function __construct(SearchFactory $factory)
{
$this->factory = $factory;
}
public function listResults(\Illuminate\Http\Request $request)
{
return $this->factory->fromRequest($request)->getResults($request);
}
}
The takeaway from this is it is very important to not involve the request in the constructors. This way you can create instances without a request in the application lifecycle. This is good for caching, testability and modularity. I also don't like the app and request methods as they pull variables out of thin air, reducing testability and performance.
class Search
{
public function __construct(){
$this->strategy = app(SearchFactory::class)->create();
}
public function getResults()
{
return $this->strategy->search();
}
}
class SearchFactory
{
public function create()
{
if (request()->missing('q')) {
return app(DatabaseSearch::class);
} else {
return app(AlgoliaSearch::class);
}
}
}
class AlgoliaSearch implements SearchInterface
{
public function __construct()
{
$this->strategy = app(AlgoliaSearchFactory::class)->create();
}
public function search()
{
$this->strategy->search();
}
}
class AlgoliaSearchFactory
{
public function create()
{
if (request('type') == 'thread') {
return app(Threads::class);
} elseif (request('type') == 'profile_post') {
return app(ProfilePosts::class);
} elseif (request()->missing('type')) {
return app(AllPosts::class);
} elseif (request()->boolean('only_title')) {
return app(Thread::class);
}
}
}
Where the classes created in the AlgoliaSearchFactory are algolia aggregators so the search method can be called on any of those classes.
Would something like this make it cleaner or even worse ?
Right now i have strategies that have strategies which sounds too much to me.
I have tried to implement a good solution for you, but I had to make some assumptions about the code.
I decoupled the request from the constructor logic and gave the search interface a request parameter. This makes the intention clearer than just pulling the Request from thin air with the request function.
final class SearchFactory
{
private ContainerInterface $container;
/**
* I am not a big fan of using the container to locate the dependencies.
* If possible I would implement the construction logic inside the methods.
* The only object you would then pass into the constructor are basic building blocks,
* independent from the HTTP request (e.g. PDO, AlgoliaClient etc.)
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
private function databaseSearch(): DatabaseSearch
{
return // databaseSearch construction logic
}
public function thread(): AlgoliaSearch
{
return // thread construction logic
}
public function threads(): AlgoliaSearch
{
return // threads construction logic
}
public function profilePost(): AlgoliaSearch
{
return // thread construction logic
}
public function onlyTitle(): AlgoliaSearch
{
return // thread construction logic
}
public function fromRequest(Request $request): SearchInterface
{
if ($request->missing('q')) {
return $this->databaseSearch();
}
// Fancy solution to reduce if statements in exchange for legibility :)
// Note: this is only a viable solution if you have done correct http validation IMO
$camelCaseType = Str::camel($request->get('type'));
if (!method_exists($this, $camelCaseType)) {
// Throw a relevent error here
}
return $this->$camelCaseType();
}
}
// According to the code you provided, algoliasearch seems an unnecessary wrapper class, which receives a search interface, just to call another search interface. If this is the only reason for its existence, I would remove it
final class AlgoliaSearch implements SearchInterface {
private SearchInterface $search;
public function __construct(SearchInterface $search) {
$this->search = $search;
}
public function search(Request $request): SearchInterface {
return $this->search->search($request);
}
}
I am also not sure about the point of the Search class. If it only effectively renames the search methods to getResults, I am not sure what the point is. Which is why I omitted it.
I had to write all this to make the problem understandable.
The SearchFactory takes all the required parameters and based on these parameters, it calls either AlgoliaSearchFactory or DatabaseSearchFactory to produce the final object that will be returned.
class SearchFactory
{
protected $type;
protected $searchQuery;
protected $onlyTitle;
protected $algoliaSearchFactory;
protected $databaseSearchFactory;
public function __construct(
$type,
$searchQuery,
$onlyTitle,
DatabaseSearchFactory $databaseSearchFactory,
AlgoliaSearchFactory $algoliaSearchFactory
) {
$this->type = $type;
$this->searchQuery = $searchQuery;
$this->onlyTitle = $onlyTitle;
$this->databaseSearchFactory = $databaseSearchFactory;
$this->algoliaSearchFactory = $algoliaSearchFactory;
}
public function create()
{
if (isset($this->searchQuery)) {
return $this->algoliaSearchFactory->create($this->type, $this->onlyTitle);
} else {
return $this->databaseSearchFactory->create($this->type, $this->onlyTitle);
}
}
}
The DatabaseSearchFactory based on the $type and the onlyTitle parameters that are passed from the SearchFactory returns an object which is the final object that needs to be used in order to get the results.
class DatabaseSearchFactory
{
public function create($type, $onlyTitle)
{
if ($type == 'thread' && !$onlyTitle) {
return app(DatabaseSearchThreads::class);
} elseif ($type == 'profile_post' && !$onlyTitle) {
return app(DatabaseSearchProfilePosts::class);
} elseif ($type == 'thread' && $onlyTitle) {
return app(DatabaseSearchThread::class);
} elseif (is_null($type)) {
return app(DatabaseSearchAllPosts::class);
}
}
}
Same logic with DatabaseSearchFactory
class AlgoliaSearchFactory
{
public function create($type, $onlyTitle)
{
if ($type == 'thread' && !$onlyTitle) {
return app(Threads::class);
} elseif ($type == 'profile_post' && !$onlyTitle) {
return app(ProfilePosts::class);
} elseif (empty($type) && !$onlyTitle) {
return app(AllPosts::class);
} elseif ($onlyTitle) {
return app(Thread::class);
}
}
}
The objects that are created by AlgoliaSearchFactory have a method search which needs a $searchQuery value
interface AlgoliaSearchInterface
{
public function search($searchQuery);
}
The objects that are created by DatabaseSearchFactory have a search method that doesn't need any parameters.
interface DatabaseSearchInterface
{
public function search();
}
The class Search now takes as a parameter the final object that is produced by SearchFactory which can either implement AlgoliaSearchInterface or DatabaseSearchInterface that's why I haven't type hinted
The getResults method now has to find out the type of the search variable ( which interface it implements ) in order to either pass the $searchQuery as a parameter or not.
And that is how a controller can use the Search class to get the results.
class Search
{
protected $strategy;
public function __construct($search)
{
$this->strategy = $search;
}
public function getResults()
{
if(isset(request('q')))
{
$results = $this->strategy->search(request('q'));
}
else
{
$results = $this->strategy->search();
}
}
}
class SearchController(Search $search)
{
$results = $search->getResults();
}
According to all of #Transitive suggestions this is what I came up with. The only thing that I cannot solve is how to call search in the getResults method without having an if statement.

How to check returned value to which function it belogns

Say I have to similar function :
public function auth(){
return $someResponse;
}
public function collect(){
return $someOtherResponse
}
Question : When one of the response get passed to another class, is there any way to check which function returned the response ?
In a purely object-oriented way, wanting to attach information to a value is akin to wrapping it into a container possessing context information, such as:
class ValueWithContext {
private $value;
private $context;
public function __construct($value, $context) {
$this->value = $value;
$this->context = $context;
}
public value() {
return $this->value;
}
public context() {
return $this->context;
}
}
You can use it like this:
function auth()
{
return new ValueWithContext($someresponse, "auth");
}
function collect()
{
return new ValueWithContext($someotherrpesonse, "collect");
}
This forces you to be explicit about the context attached to the value, which has the benefit of protecting you from accidental renamings of the functions themselves.
As per my comment, using arrays in the return will give you a viable solution to this.
It will allow a way to see what has been done;
function auth()
{
return (array("auth" => $someresponse));
}
function collect()
{
return (array("collect" => $someotherrpesonse));
}
class myClass
{
function doSomething($type)
{
if (function_exists($type))
{
$result = $type();
if (isset($result['auth']))
{
// Auth Used
$auth_result = $result['auth'];
}
else if (isset($result['collect']))
{
// Collect used
$collect_result = $result['collect'];
}
}
}
}
It can also give you a way to fail by having a return array("fail" => "fail reason")
As comments say also, you can just check based on function name;
class myClass
{
function doSomething($type)
{
switch ($type)
{
case "auth" :
{
$result = auth();
break;
}
case "collect" :
{
$result = collect();
break;
}
default :
{
// Some error occurred?
}
}
}
}
Either way works and is perfectly valid!
Letting the two user defined functions auth() & collect() call a common function which makes a call to debug_backtrace() function should do the trick.
function setBackTrace(){
$backTraceData = debug_backtrace();
$traceObject = array_reduce($backTraceData, function ($str, $val2) {
if (trim($str) === "") {
return $val2['function'];
}
return $str . " -> " . $val2['function'];
});
return $traceObject;
}
function getfunctionDo1(){
return setBackTrace();
}
function getfunctionDo2(){
return setBackTrace();
}
class DoSomething {
static function callfunctionTodo($type){
return (($type === 1) ? getfunctionDo1() : getfunctionDo2());
}
}
echo DoSomething::callfunctionTodo(1);
echo "<br/>";
echo DoSomething::callfunctionTodo(2);
/*Output
setBackTrace -> getfunctionDo1 -> callfunctionTodo
setBackTrace -> getfunctionDo2 -> callfunctionTodo
*/
The above function would output the which function returned the response

How to forward a PHP closure to another controller

I'm trying to make a very basic routing class and learn PHP closures by example. Basically, I want to make a routing feature like in Laravel but only with using closures.
function get($uri)
{
if($uri == '/account')
{
return true;
}
else
{
return false;
}
}
function Boo()
{
echo "Boo";
}
$route = new Route();
$route->get('/account', function() {
return $route->Boo();
});
I can do this without closures and see "Boo" as output.
How can I do this with closures? I currently see a blank output.
Ps. Functions are in correct class.
You need to actually accept the closure as a parameter of your get method, and call it, here's an example
class Route
{
function get($uri, Closure $closure=null)
{
if($uri == '/account')
{
// if the closure exists, call it, passing it this instance as its parameter
if (null !== $closure) {
$closure($this);
}
return true;
}
else
{
return false;
}
}
function Boo()
{
echo "Boo";
}
}
$route = new Route();
// have the closure accept a route as it's parameter
$route->get('/account', function($route) {
return $route->Boo();
});

How do I make PHP's Magic __set work like a natural variable?

Basically, what I want to do is create a class called Variables that uses sessions to store everything in it, allowing me to quickly get and store data that needs to be used throughout the entire site without working directly with sessions.
Right now, my code looks like this:
<?php
class Variables
{
public function __construct()
{
if(session_id() === "")
{
session_start();
}
}
public function __set($name,$value)
{
$_SESSION["Variables"][$name] = $value;
}
public function __get($name)
{
return $_SESSION["Variables"][$name];
}
public function __isset($name)
{
return isset($_SESSION["Variables"][$name]);
}
}
However, when I try to use it like a natural variable, for example...
$tpl = new Variables;
$tpl->test[2] = Moo;
echo($tpl->test[2]);
I end up getting "o" instead of "Moo" as it sets test to be "Moo," completely ignoring the array. I know I can work around it by doing
$tpl->test = array("Test","Test","Moo");
echo($tpl->test[2]);
but I would like to be able to use it as if it was a natural variable. Is this possible?
You'll want to make __get return by reference:
<?php
class Variables
{
public function __construct()
{
if(session_id() === "")
{
session_start();
}
}
public function __set($name,$value)
{
$_SESSION["Variables"][$name] = $value;
}
public function &__get($name)
{
return $_SESSION["Variables"][$name];
}
public function __isset($name)
{
return isset($_SESSION["Variables"][$name]);
}
}
$tpl = new Variables;
$tpl->test[2] = "Moo";
echo($tpl->test[2]);
Gives "Moo".

PHP How to distinguish $this pointer in the inheritance chain?

Please look at the following code snipped
class A
{
function __get($name)
{
if ($name == 'service') {
return new Proxy($this);
}
}
function render()
{
echo 'Rendering A class : ' . $this->service->get('title');
}
protected function resourceFile()
{
return 'A.res';
}
}
class B extends A
{
protected function resourceFile()
{
return 'B.res';
}
function render()
{
parent::render();
echo 'Rendering B class : ' . $this->service->get('title');
}
}
class Proxy
{
private $mSite = null;
public function __construct($site)
{
$this->mSite = $site;
}
public function get($key)
{
// problem here
}
}
// in the main script
$obj = new B();
$obj->render();
Question is: in method 'get' of class 'Proxy', how I extract the corresponding resource file name (resourceFile returns the name) by using only $mSite (object pointer)?
What about:
public function get($key)
{
$file = $this->mSite->resourceFile();
}
But this requires A::resourceFile() to be public otherwise you cannot access the method from outside the object scope - that's what access modifiers have been designed for.
EDIT:
OK - now I think I do understand, what you want to achieve. The following example should demonstrate the desired behavior:
class A
{
private function _method()
{
return 'A';
}
public function render()
{
echo $this->_method();
}
}
class B extends A
{
private function _method()
{
return 'B';
}
public function render()
{
parent::render();
echo $this->_method();
}
}
$b = new B();
$b->render(); // outputs AB
But if you ask me - I think you should think about your design as the solution seems somewhat hacky and hard to understand for someone looking at the code.

Categories