Fetch class methods from an extended class from within a used Trait - php

I have a trait AutomatedEmails with a submit function, part of which features the below:
AutomatedEmails.php (Trait):
trait AutomatedEmails
{
/** Initial route function the form is posted to */
public function submit(Request $request)
{
$slug = $request->slug;
// Fetch the action function required and load (from AutomatedEmailsController file)
// The function must exactly match the slug in the automated_emails_actions table
// with underscores rather than hyphens
if (!method_exists($this, str_replace('-', '_', $slug)))
return redirect()->to($request->redirectUrl)->with('danger', App::environment('local') ? 'Form processing function not set up for this form' : $liveProcessingError);
// THE LINE ABOVE IS THE PROBLEM LINE - THE METHOD DOES NOT EXIST
}
I have a class which is also part of this package which contains form processing methods:
AutomatedEmailsController.php:
class AutomatedEmailsController extends Controller
{
use AutomatedEmails;
public $fillables = [];
public $appends = [];
public $tracking_data = [];
public $action;
public $thank_you = ['message' => 'Thank you - your enquiry has been received'];
public function contact_enquiry(Request $request = null)
{
// method implementation
}
}
I then want to create a class extension on the local project but make these methods available to the Trait (and this is where the the problem lies):
AutomatedEmailsControllerExtension.php:
class AutomatedEmailsControllerExtension extends AutomatedEmailsController
{
use AutomatedEmails;
public function vehicle_enquiry(Request $request = null)
{
// method implementation
}
}
Here is the new form example:
<form method="post" name="vehicle_enquiry" action="{{route('automated-email', ['slug' => 'vehicle_enquiry'])}}">
<input type="hidden" name="vehicle_id" value="{{$vehicle->id}}">
<-- Additional inputs here -->
</form>
And this is the route method in web.php:
Route::post('/automated-enquiry', 'AutomatedEmailsControllerExtension#submit')->name('automated-email');
The purpose of this class is to add functions that pass through data about a new form that should be processed.
The problem is that the vehicle_enquiry method cannot be found on the AutomatedEmails.php Trait and therefore the form will not get processed as required.
What I am planning to do is suggest a file for the Trait to search for an instantiate methods from that file.

It is entirely possible to refer to methods defined in composited classes from a trait (or from a parent class to methods on children classes).
See this, for example:
trait Foo {
public function g($method = 'zbar') {
if (method_exists($this, $method)) {
echo call_user_func([$this, $method]), "\n";
}
else {
echo "we not good";
}
}
}
class Bar {
use Foo;
protected function zbar() {
return 'abc';
}
}
class SuperBar extends Bar {
protected function xbar() {
return 'edf';
}
}
$a = new Bar();
$a->g('zbar');
$b = new SuperBar();
$b->g('xbar');
This outputs:
abc
edf
You can see it working here.
In your case, it looks like your code is not going through AutomatedEmailsControllerExtension at all. To make sure, you can put a die(class_name($this)); statement on submit().
If you are not hitting that class, it could be because of Laravel's caching getting in the way. Clear cache and you will be good to go.
Additionally, since AutomatedEmailsControllerExtension extends on AutomatedEmailsController, there is no need to use the trait again. If it's used on the parent, the methods are already available on the children.

Related

Is it possible to invoke an instance of a class whose name is passed as a variable?

I'm developing in Laravel 9, though I assume this is Php-specific. Example below of what I'm trying to achieve: Imagine I have a controller named HomeController.php with a getData() method that returns something I need...
<?php
namespace App\Http\Controllers;
class HomeController
{
public function getData()
{
return [my data]
}
}
And I want to be able to call that class and method in a dynamic way, and assign my data to $data...
<?php
use App\Http\Controllers\HomeController;
class Example
{
public $className = 'HomeController';
public $method = 'getData';
public function index()
{
$instance = new $this->className;
$method = $this->method;
$data = $instance->$method();
}
}
I have a variation of this setup in my application, and it's not working. I get the following error: Class "HomeController" not found.
If I replace $this->className with HomeController it works. Keep in mind $className will be passed from elsewhere, I want to avoid hard-coding class names into my Example class.
It is true that I will still need to include them all at the top anyway, but I just want to know if it's possible to pass a class name like that. Unless there's a way to dynamically include those too, but I doubt it.
Edit: Tim's answer in the comments worked great. Here is a fixed version:
<?php
use App\Http\Controllers\HomeController;
class Example
{
public $className = 'App\\Http\\Controllers\\HomeController'; // Change 1
public $method = 'getData';
public function index()
{
$instance = app()->make($this->className); // Change 2
$method = $this->method;
$data = $instance->$method();
}
}

PHP/OOP - Using parent class to run child functions

I'm looking for a way to have a single base class that can be extended by several child classes, only one of which would be active at a time. A very basic example:
class API_Base {
public $context;
public function __construct() {
$this->init()
}
}
class Mailchimp_API extends API_Base {
public function init() {
$this->context = 'mailchimp';
$this->enabled = false;
}
public function add_contact($email_address) {
// mailchimp API for adding contact
}
}
class Infusionsoft_API extends API_Base {
public function init() {
$this->context = 'infusionsoft';
$this->enabled = true;
}
public function add_contact($email_address) {
// infusionsoft API for adding contact
}
}
Each child initializes itself and registers as an option for the user to select. After the user has chosen which integration to use, this is saved to the database. I'd like future access to the API_Base to look something like:
$api = new API_Base();
$api->context; // should be "infusionsoft"
$api->add_contact($email_address);
So when $api->add_contact() is run, it only runs the add_contact() function for the active API integration.
Eventually I'd like to somehow use get_class_methods(); to return the capabilities of just the active API, so functions accessing the API can know what is possible (i.e. some API's support email lists while others don't, or support creating custom fields, etc.).
I've had some success with calling parent::set_context($context); from the enabled class, but I still can't figure out how to get the parent to only execute the methods in the "enabled" child class.
This is not how inheritance works. Child subclasses inherit from their parent class.
To solve your problem you can add a factory method to API_Base which will create API implementation by its type:
class API_Base {
public static function createByType($type)
{
switch ($type) {
case 'mailchimp': return new Mailchimp_API();
case 'infusionsoft': return new Infusionsoft_API();
default: throw new \InvalidArgumentException(spintf('Invalid API type "%s"', $type));
}
}
// other methods
}
and use it like this:
$api = API_Base::createByType($user->selectedApi);
$api->context; // should be "infusionsoft"
$api->add_contact($email_address);
You can consider Abstract Class Implementation . The abstract class works as the , who ever is extending the abstract class can execute the methods it have .
abstract class Something{
function __construct(){
// some stuff
}
function my_func(){
$this->myTest ;
}
abstract function my_func();
}
class Some extends Something{
function __construct(){
parent::__construct() ;
}
function my_test(){
echo "Voila" ;
}
}
I got it working in a way works perfectly for me, thanks to Ihor's advice. Here's what I ended up doing:
In the main plugin file, there's a filterable function where other devs can add new integrations if they need. The first parameter is the slug (for my autoloader) and the second is the class name.
public function get_apis() {
return apply_filters( 'custom_apis', array(
'infusionsoft-isdk' => 'MYPLUGIN_Infusionsoft_iSDK',
'infusionsoft-oauth' => 'MYPLUGIN_Infusionsoft_oAuth',
'activecampaign' => 'MYPLUGIN_ActiveCampaign'
) );
}
Each integration contains the slug and the class name. Then in my API_Base class I have this in the constructor:
class API_Base {
public $available_apis = array();
public $api;
public function __construct() {
$configured_apis = main_plugin()->get_apis();
foreach( $configured_apis as $slug => $classname ) {
if(class_exists($classname)) {
$api = new $classname();
$api->init();
if($api->active == true)
$this->api = $api;
$this->available_apis[$slug] = array( 'name' => $api->name );
if(isset($api->menu_name)) {
$this->available_apis[$slug]['menu_name'] = $api->menu_name;
} else {
$this->available_apis[$slug]['menu_name'] = $api->name;
}
}
}
}
}
And in my main file, after all the includes, I run:
self::$instance->api_base = new API_Base();
self::$instance->api = self::$instance->api_base->api;
Now I can call self::$instance->api->add_contact($email); and it will trigger whichever is the current active API.
It seems to be the best approach as this way I can spin up the API only once when the plugin loads, instead of having to create a new instance each time I want to use it.

Get all methods

I'm attempting to build a role based access control in our PHP framework. The framework is on MVC architecture so every path works on /controller/action/param. We can get the controller and action on initialization and store them in variables, $controller, $action. Now my idea is to use a class to check the permissions of this action like:
Auth::permissions($controller, $action);
Now I'm hoping I could somehow create a script which would find all public methods of controllers inside a /modules/ folder. This way I could just run a script and it would update all controller actions as a list to the database, where we would get the role permissions from. This way I could avoid inserting all controller actions manually. Getting all the controllers is very easy as the folder structure is as:
/modules
/controller
controller.php
So I can just find all subdirectories on modules and add .php in the end. My question is that can I get the file's public methods somehow?
class Example extends Controller {
public function main() {
return 'foo';
}
}
This way I could store this in the database as
example | main | role_id
Here is a little code that can help you:
<?php
class Example {
public function main() {
return 'foo';
}
private function privatefunc(){
}
public function anotherpublicfunc(){
}
}
$reflector = new ReflectionClass("Example");
foreach($reflector->getMethods() as $method){
if($method->isPublic()) {
echo "Method ".$method->name." is public".PHP_EOL;
}else{
echo "Method ".$method->name." is not public".PHP_EOL;
}
}
?>
output:
Method main is public
Method privatefunc is not public
Method anotherpublicfunc is public
If you want to get public methods of a class then you can use get_class_methods read the doc here
class Car {
public function permission_method_two() {
}
public function permission_method_three() {
}
private function private_function() {
}
}
echo '<pre>'.print_r(get_class_methods('Car'),1).'</pre>';
// prints only public methods:
Array
(
[0] => permission_method_two
[1] => permission_method_three
)
You can follow convention:
- each public methods start without lowdash
- each private and protected method start with lowdash
Example
class Example
{
public function publicMethod()
{
}
private function _privateMethod()
{
}
protected function _protectedMethod()
{
}
}
and then use http://php.net/manual/ru/function.get-class-methods.php
foreach(get_class_methods('Example') as $methodName){
if(strpos($methodName, '_') !== 0) $publicMethod[] = $methodName;
}

How to add functionality to an object in PHP

I am creating a web site that essentially sells advertising 'spots'. I.e someone can signup and buy a banner advert to be displayed on the home page, or they can buy an advert where they get their own profile page. My point being, although all adverts share common functionality, they do differ.
To accomplish this, my domain model looks like this: (simplified)
class Advert {
protected
$uID,
$startTime,
$traits = array();
public function __construct($_traits) {
$this->traits = $_traits;
}
public function getUID() { return $this->startTime; }
public function getStartTime() { return $this->startTime; }
public function setStartTime($_startTime) { $this->startTime = $_startTime; }
public function save() {
MySQLQuery 'UPDATE adverts SET startTime = $this->startTime WHERE uID = $this->uID';
foreach($this->traits as $trait) {
$trait->save($this->uID);
}
}
....
}
-
interface IAdvertTrait {
public function save($_advertUID);
}
-
class AdvertTraitProfile implements IAdvertTrait {
protected $url;
public function getURL() { return $this->url; }
public function setURL($_url) { $this->url = $_url; }
public function save($_advertUID) {
MySQLQuery 'UPDATE advertdata_profile SET url = $this->url WHERE advertUID = $_advertUID';
}
....
}
-
class AdvertTraitImage implements IAdvertTrait {
protected $image;
public function getImage() { return $this->image; }
public function setImage($_image) { $this->image = $_image; }
public function save($_advertUID) {
MySQLQuery 'UPDATE advertdata_image SET image = $this->image WHERE advertUID = $_advertUID';
}
....
}
There are actually several 'AdvertTrait...' classes, all of which implement IAdvertTrait.
As you can see, if I create an advert like this:
$advert = new Advert(
array(
new AdvertTraitProfile(),
new AdvertTraitImage()
...
)
);
I can then do this:
$advert->save();
And all the required information will get saved to the DB by the Advert itself and each of its AdvertTraits.
Using this method I'm able to create different kinds of advert simply by passing in different 'traits'. However, to my problem - I've no idea how I should go about manipulating an Advert. As per the example above, there is really no point creating and advert and then immediately saving it.
I'd like to be able to this:
$advert->getStartTime(); # Works
$advert->getURL(); # Doesn't work of course, as the getURL method is encapsulated within a property of the Advert's 'traits' array
$advert->setImage('blah.jpg'); # Also does not work
I'm not sure how to go about making these 'internal' methods accessible.
I could just create a different 'Advert' class for each kind of advert i.e:
AdvertProfile extends Advert {
$this->traitProfile = new AdvertTraitProfile();
public function getURL() { return $this->traitProfile->getURL(); }
...
}
AdvertImage extends Advert {
$this->traitImage = new AdvertTraitImage();
public function getImage() { return $this->traitImage->getImage(); }
...
}
AdvertProfileImage extends Advert {
$this->traitProfile = new AdvertTraitProfile();
$this->traitImage = new AdvertTraitImage();
public function getURL() { return $this->traitProfile->getURL(); }
public function getImage() { return $this->traitImage->getImage(); }
...
}
But I feel this is going to get messy; I'd need to keep creating new 'Advert' classes for every combination of traits I need and each advert class would need to define its trait methods in itself so they can be called from an instance of the advert.
I've also messed with the decorator pattern; so instead of passing these 'trait' classes to the constructor of the Advert, I chain the decorators together like:
$advert = new AdvertImageDecorator(new AdvertProfileDecorator(new Advert()));
However this requires the decorators to be able to 'lookup' methods that don't belong to them using method_exists and call_user_func_array which just seems like a big old hack to me. Plus chaining a multitude of decorators together like that just grates on me.
I've also had a look at proper PHP Traits, but IMVHO I do not think they'll help me. For example, every AdvertTrait has a 'save' method, all of which need to be called at the same time. I believe a proper Trait would require me to pick just one 'save' method from one trait.
Maybe I should use plain old inheritance - but then I'd still be creating specific types of Advert, all of which ultimately inherit from Advert. However I believe this would cause further issues; i.e I would not be able to make a AdvertWithProfileAndImageTraits extend from both AdvertWithProfileTraits AND AdvertWithImageTraits.
Can anyone offer a proper solution to this conundrum? Perhaps there is another design pattern I should be using.
Thanks very much,
Dave
I would go for the Decorator approach.
An abstract AdvertDecorator class can look like this:
abstract class AdvertDecorator implements IAdvertTrait {
protected $child;
public function __construct($child=null) {
if(!$child) {
$child = new NullAdvert();
}
$this->child = $child;
}
/**
* With this function all calls to non existing methods gets catched
* and called on the child
*/
public function __call($name, $args) {
return call_user_func_array(array($this->child, $name), $args);
}
}
/**
* This class is for convenience so that every decorator
* don't have to check if there is a child
*/
class NullAdvert implements IAdvertTrait {
public function save($_advertUID) {
// do nothing
}
}
Instead of the NullAdvert class you can use a BaseAdvert class, which implements all of your basic advert logic (like you have done in the Advert class).
Now all other classes extend from this AdvertDecorator class:
class AdvertProfile extends AdvertDecorator {
public function getProfileURL() { ... }
public function save($_advertUID) {
// save own advert
MySQLQuery 'UPDATE advertdata_profile SET url = $this->url WHERE advertUID = $_advertUID';
// save advert of child
$this->child->save($_advertUID);
}
}
class AdvertImage extends AdvertDecorator {
public function getImage() { ... }
public function save($_advertUID) {
// save own advert
MySQLQuery 'UPDATE advertdata_image SET image = $this->image WHERE advertUID = $_advertUID';
// save advert of child
$this->child->save($_advertUID);
}
}
class AdvertProfileImage extends AdvertDecorator {
public function getProfileImageURL() { ... }
public function getProfileImage() { ... }
public function save($_advertUID) {
// save own advert ...
// save advert of child
$this->child->save($_advertUID);
}
}
You can use it like this:
$advert = new AdvertProfile();
$advert = new AdvertImage($advert);
$advert = new AdvertProfileImage($advert);
// save all advert components
$advert->save('uid');
// call functions
$advert->getProfileURL();
$advert->getImage();
$advert->getProfileImageURL();
$advert->getProfileImage();
This structure is IMHO very flexible. Every Advert Component can be added to the current Advert in arbitrary order. Futhermore you can extend this solution with the composite pattern and add a AdvertComposite so that you can group your components. You can even add multiple Advert Components of the same kind to one Advert (for this you have to change the methods a little bit).

How do I use a variable within an extended class public variable

Have a class that I am using, I am overriding variables in the class to change them to what values I need, but I also not sure if or how to handle an issue. I need to add a key that is generated to each of this URLs before the class calls them. I cannot modify the class file itself.
use Theme/Ride
class ETicket extends Ride {
public $key='US20120303'; // Not in original class
public $accessURL1 = 'http://domain.com/keycheck.php?key='.$key;
public $accessURL2 = 'http://domain.com/keycheck.php?key='.$key;
}
I understand that you cannot use a variable in the setting of the public class variables. Just not sure what would be the way to actually do something like this in the proper format.
My OOP skills are weak. I admit it. So if someone has a suggestion on where I could read up on it and get a clue, it would be appreciated as well. I guess I need OOP for Dummies. =/
---- UPDATE ---
The initial RIDE class has 2 URLs set.
public $accessURL1 = "http://domain.com/index.php";
public $accessURL2 = "http://domain.com/index2.php";
I was to override them so the RIDE class will use my new domains.
I can add the following and it works...
class ETicket extends RIDE {
public $accessURL1 = 'http://mydomain.com/myindex.php';
public $accessURL2 = 'http://mydomain.com/myindex2.php';
}
However, I also want to pass a variable from elsewhere ($key) as a parameter to the URL when I override them so when i call RIDE it has a URL with the value of KEY at the end. (?key=keyvalue)
Your close, if you do not want to allow calling code to change the $key, you can do something like:
class ETicket extends Ride {
public function getKey()
{
return 'US20120303';
}
public function generateUrl()
{
return 'http://domain.com/keycheck.php?key=' . $this->getKey();
}
}
// Calling code example
$eTicket= new ETicket();
// $key is a member of ETicket class, so just call on generateUrl which will
// build and return the url
var_dump($eTicket->generateUrl());
You can also permit calling code to change the key if needed, by adding a public setter/getter:
class ETicket extends Ride {
protected $key;
public function setKey($key)
{
$this->key = $key;
}
public function getKey()
{
return $this->key;
}
public function generateUrl()
{
return 'http://domain.com/keycheck.php?key=' . $this->getKey();
}
}
// Calling code example
$eTicket= new ETicket();
$eTicket->setKey('US20120303');
var_dump($eTicket->generateUrl());
-- UPDATE --
There are a couple of options, you can either append the key to your url as part of the calling code, like this:
$eTicket= new ETicket();
$url = $ride->accessURL1 . '?key=US20120303';
Or, use a method (changed slightly to accept key directly) as I described earlier:
class ETicket extends Ride
{
public function generateUrl($key)
{
return $this->accessURL1 . '?key=' . $key;
}
}
$eTicket= new ETicket();
$url = $eTicket->generateUrl('US20120303');
I guess the point is, you cannot do what you originally asked without which is to concatenate a variable to a member variable initialization.

Categories