5

I want to be able to use the trait if it's available.

Obviously i cannont define that inside the class itself (syntax Error)

//fails
include_once('myTrait.php');

class foo
{ 
    var $bar; 

    if (trait_exists('myTrait')) {
        use myTrait;
    }
}

//also fails
foo use myTrait;

//also fails
$f = new foo();
$f use myTrait;

//also fails
$f = new foo() use myTrait;

Ideal case scenario would be something like this:

class foo
{
    var $bar;
}

if (file_exists('myTrait.php')) {
   include_once('myTrait.php');
   //make class foo use myTrait;
}

$f=new foo();

Having hard time finding documentation and traits doesn't seems very popular but in my particular case they are very useful. I also try to keep resource as low a possible by only including files if needed.

Hints, documentation and explanation welcome as usual.


The closest my search brought me was in this article http://brendan-bates.com/traits-the-right-way/

Let's say a few of these controllers (but not all of them) require a database connection. To keep performance up, we shouldn't give every controller the database connection. What we could do is write an abstract class which extends BaseController which provides a database connection. But, in the future, what if an object that is not a controller requires a database connection? Instead of duplicating this logic, we can use horizontal reuse.

A simple trait can be created:

trait DatabaseAware 
{    
    protected $db;

    public function setDatabase($db) 
    {
        $this->db = $db;
    }

    protected function query($query) 
    {
        $this->db->query($query);
    }
}

This trait now provides classes with common database functionality. Any class which requires a database connection, be it a controller or a manager (or anything), can use this trait:

class IndexController extends BaseController 
{
    use DatabaseAware;

    public function indexAction() 
    {
        $this->query("SELECT * FROM `someTable`");
    }
}

Where as I implement traits depending on the needs of my different objects. Database connection, debugging reporting, etc.

localheinz
  • 9,179
  • 2
  • 33
  • 44
Louis Loudog Trottier
  • 1,367
  • 13
  • 26
  • 1
    you can define a whole class depending on trait existence (inside the `if(trait_exists('myTrait'))`) – Deadooshka Aug 16 '17 at 00:28
  • 1
    A `trait` "enhances" a class with new methods. How do you intend to use the methods if you don't know when you write the code if the class provides them or not? It seems to me that you are trying to solve in a wrong way a completely different problem. – axiac Aug 18 '17 at 23:09
  • *"I also try to keep resource as low a possible by only including files if needed."* -- this is the responsibility of an autoloader. – axiac Aug 18 '17 at 23:10
  • @axiac This could be solved by implementing an interface - either the trait provides the implementation, if not, it's implemented otherwise. – localheinz Aug 18 '17 at 23:43
  • 2
    A trait is not a dynamic feature. It either exists or it doesn't. If it doesn't exist then neither the classes that `use` exist; they cannot be loaded and used. If the trait exists then the entire question doesn't make any sense. Use it where you need it and don't bother checking its existence. If you need a feature that may or may not be available (because of an external cause, not because the code doesn't exist) then the solution uses other techniques: [DI](https://en.wikipedia.org/wiki/Dependency_injection) and [Strategy](https://en.wikipedia.org/wiki/Strategy_pattern) design pattern. – axiac Aug 19 '17 at 00:02
  • @axiac That is why i want to check if the 'code exist' before making the class `use` the trait... consider the scenario where common method are used to dump debug info. On a live system the debug trait (file) won't exist. If the debugging trait ain't available the class should not try to `use` the trait. --- On a dev system i want to be able to use the debugging methods (from the trait) without having to edit every single classes that i want to diagnose. Are you still suggesting a better approach? Also, Could you recommend a link or documentation about autoloader? ty again. – Louis Loudog Trottier Aug 19 '17 at 01:42
  • Might be Related, most likely shooting myself in the foot and get marked as dupes: https://stackoverflow.com/questions/14355029/is-it-possible-to-add-traits-to-a-class-in-php-in-runtime Still open for bounty as i'm looking for the most straight forward way to achieve this. – Louis Loudog Trottier Aug 20 '17 at 04:11

4 Answers4

7

Easy!

Trait

A trait that might be available or not, will be either used or not, but will eventually help to implement an interface:

<?php

trait BarTrait
{
    public function bar()
    {
        return 'Hey, I am your friend!';
    }
}

Interface

An interface we are looking to implement:

<?php

interface BarInterface
{
    /**
     * @return string
     */
    public function bar();
}

Class using trait

A class FooUsingBarTrait which uses a trait BarTrait to implement the aforementioned interface:

<?php

class FooUsingBarTrait implements BarInterface
{
    use BarTrait;
}

Class not using trait

A class FooNotUsingBarTrait which does not use a trait BarTrait, but instead implements the aforementioned interface itself:

class FooNotUsingBarTrait implements BarInterface
{
    public function bar()
    {
        return 'Hey, I am one of your friends!';
    }
}

Conditionally create class definition

Finally, conditionally define a class Foo, depending on whether a trait BarTrait exists or not:

<?php

if (trait_exists(BarTrait::class) {
    class Foo extends FooUsingBarTrait
    {
    }
} else {
    class Foo extends FooNotUsingBarTrait
    {
    }
}

Create your instance

$foo = new Foo();

$foo->bar();

var_dump(
    get_class($foo),
    class_parents(Foo::class)
);

Note This probably makes most sense if both classes FooUsingBarTrait and FooNotUsingBarTrait implement a common interface - after all, you probably want to provide some functionality which will be shared between the two implementations: one using a trait, the other by other means (methods provided by that class).

For reference, see:

For examples, see:

localheinz
  • 9,179
  • 2
  • 33
  • 44
  • A lot of documentation talks about interface used with traits. I will dig more in the interface's documentation. Most of your answer is stuff i already knew but viewed from another angle. – Louis Loudog Trottier Aug 19 '17 at 01:05
  • 1
    Hehe, glad to be of service - hope the solution works for you, @LouisLoudogTrottier! – localheinz Aug 19 '17 at 09:00
1

Your question is fun, and eval() likely meets your needs. This style using code generation is ugly, but I know it works because I verified it myself on my own machine. Here's how you can do it:

$src = '
class foo {
  var $bar; // and all of your other code goes here
';
if (file_exists('myTrait.php')) {
  include_once('myTrait.php');
  $src .= "use myTrait;\n";
}
$src .= "}";
eval ($src); // your class finally gets declared

I don't use eval() often, but it's fun when it solves a problem that otherwise cannot be conventionally solved.

Steven
  • 2,054
  • 1
  • 18
  • 13
  • 1
    And i presume $src can be anything interpreted as a String? `ob_start(); include 'src/class.php'; $src=ob_get_contents(); ...` My question was maybe fun but i'll have even more fun trying this and tweaking it to fit my lazyness. Wasn't a big deal but will definitively try this out since it's what seems the nearest solution to what i'm looking for. – Louis Loudog Trottier Aug 22 '17 at 02:07
  • Thank you, a little fiddling and i was able to mix that into a single liner. – Louis Loudog Trottier Aug 23 '17 at 01:08
  • Also, now the real fun begins, knowing how to exploit it and when NOT to use it. – Louis Loudog Trottier Aug 23 '17 at 01:33
  • 1
    I'm glad it worked out for you! Looks like you improved on my suggested answer. Based on your comments, I'm pretty sure I know what you did, and I think that's nifty! – Steven Aug 29 '17 at 21:28
1

no matter how bad it is, you can do it by extending the class.


trait AutoPilot {
   function navigate() {
       echo 'navigating...';
   }
}

if (trait_exists('AutoPilot')) {
   class Machine {
       use AutoPilot;
   }
} else {
   class Machine {
   }
}

class Car extends Machine {
}


$car = new Car;
$car->navigate();
-2

Here what i've ended up with :

eval("class myClass {" 
    . (trait_exists('myTrait') ? "use myTrait;" : "") 
    . str_replace(['class ', '<?php'], '//', file_get_contents(myClass.php"))
);

Total lazyness:

  • Duplicate trait_exists line to add more traits
  • Comments out the class keyword and the <?php tag so you don't have to edit the class file
  • evaluates the one long line of smelly code.

This works just fine for me and 'as is' without any modification to any file except the one i paste this line in. It will probably won't be the case for you.

Consider the fact that:

  • don't use closing php tag
  • only one class by file
  • you need to add any other keywords (extends, implements,...)
  • and probably way more unexpected behaviour depending on your code

Thanks to lacalheinz for his instructive post but Steven aimed at the bulleyes with eval().

Louis Loudog Trottier
  • 1,367
  • 13
  • 26