4

I am trying to understand Wordpress structure in more details. As advised to me, using global keyword is not a good idea.

Here is what I am trying to accomplish:

Inside functions.php

$affID = '12334'; //defining a variable

add_shortcode('affiliate_link', function() {
   $newLink = 'https://example.com?id=' . $affID;
   return $newLink;
}

When I run the shortcode, I only get https://example.com?id= part of it. Of course, I can pass the $affID as an argument, but I would like to set up a big list of variables that I am going to use inside different functions, and I think it's not a good idea to pass such a huge list of arguments.

Mohit
  • 117
  • 1
  • 6

1 Answers1

4

Here are just a few Ideas. Really this is a problem of "scope" more than anything else.

USE

Simply make use of the use part of the closure:

$affID = '12334'; //defining a variable

add_shortcode('affiliate_link', function() use ($affID){
  $newLink = 'https://example.com?id=' . $affID;
  return $newLink;
});

I was actally happy when I seen the closure, this is probably the easiest way.

CLASS

Another way would be to maintain the state of it using a class instance (you could do it static if you really wanted to). This makes sense if you need a class for this functionality or the general affiliate system any way. Then you can just tack this in, with no big deal:

class foo{
     protected $affID = '12334';

     public function affiliate_link_callback(){
          $newLink = 'https://example.com?id=' . $this->affID;
          return $newLink;
     }
}

//to use a class as a call back it works like call_user_func, [obj, method]
add_shortcode('affiliate_link', [new foo, 'affiliate_link_callback']);

GLOBAL

Least desirable would be to use a global, which I personally detest and never use them (but it is a option, I suppose). Globals are bad because it can be a real challenge to find out (or track) where they were set (or modified) which makes debugging it a real chore.

global $affID;     
$affID = '12334'; //defining a variable

add_shortcode('affiliate_link', function(){
   global $affID;
   $newLink = 'https://example.com?id=' . $affID;
   return $newLink;
});

CONSTANT

You can define it as a constant, this only really makes sense if you use it a lot in other parts of your code as it makes maintaining it easier. It's not impossible you would need this ID in several places, and a constant gives you a global way to handle it. It's also conceivable that you wouldn't be modifying this at run time. It's better then a "global" because it can't be changed at runtime, so that negates the problems with keeping track of where it was changed in your code (because it cant be). Of course it has it's own set of limitations because of that. Anyway it's simple to do:

define('AFF_ID','12334'); //defining a variable

add_shortcode('affiliate_link', function(){
   global $affID;
   $newLink = 'https://example.com?id=' . AFF_ID;
   return $newLink;
});

DATABASE

And last depending on what $affID is you may be able to store it in wordpress DB. I would probably go for the user meta so it's linked to a users account. But, if it's a global value for the site, you could use the the options setting stuff instead (I would have to look that up though, example). Don't quote me on the code for this one as I forget exactly what get_user_meta returns, but I know you should almost always return a single value (the third arg should be true)

add_shortcode('affiliate_link', function(){
   $meta = get_user_meta(get_current_user_id(), 'affiliate_id', true);
   $newLink = 'https://example.com?id=' . $meta['affiliate_id'];
   return $newLink;
});

Of course in this last example you have to save the data at some point, but I am sure you can figure that out.

SHORTCODE ATTR

I think you mentioned this in the question, but you can send it as part of the shortcode too:

add_shortcode('affiliate_link', function($attr){
    extract(shortcode_atts( array(
        'id' => false,
    ), $atts, 'affiliate_link' ));

   if(!$affiliate_id) return '';

   $newLink = 'https://example.com?id='.$id;
   return $newLink;
});

I used extract above, which is a fun little function PHP has. I wouldn't recommend it for everything as it can pollute your variables and there are some security concerns.

http://php.net/manual/en/function.extract.php

Import variables from an array into the current symbol table.

Checks each key to see whether it has a valid variable name. It also checks for collisions with existing variables in the symbol table. Warning

Do not use extract() on untrusted data, like user input (e.g. $_GET, $_FILES).

Basically it takes ['id'=>'12345'] and makes a variable named $id that has 12345 as it's value (in this example). You don't have to use it, and I generally avoid it. But I thought it would be fun to use in this example.

Other Thoughts

One additional thought is that I would return the link in it's entirety instead of just the href. So instead of doing this

 <a href="[affiliate_link]">Link</a>

You would do this:

 [affiliate_link]Link[/affiliate_link]


 //  [affiliate_link id="12345" ]Link[/affiliate_link]

The main reason is in the first case, if your shortcode fails it will leave a useless link on the page. A user won't be able to tell this unless they pay close attention to the actual link destination. However, if you generate teh entire link and it fails, nothing gets put on the page. This also works nice for users that don't have an affiliate id (if you use the user meta example, and it's linked to that). In this case nothing would show for the link if they don't have the affiliate Id, but if they do the link would show. As I mentioned if you put the link in the post and then they didn't have it, you would still get a link, but with an empty destination (hope that makes sense).

If you do that then I would add the content in as well, for the link text. Using the last example (above):

add_shortcode('affiliate_link', function($attr, $content=''){
    extract(shortcode_atts( array(
        'id' => false,
    ), $atts, 'affiliate_link' ));

   if(!$affiliate_id) return '';

   $newLink = '<a href="https://example.com?id='.$id.'">'.$content.'</a>';
   return $newLink;
});

 //this shortcode
//[affiliate_link id="12345" ]Link[/affiliate_link]

 //generates this link
//<a href="https://example.com?id=123345">Link</a>

That said, I have no idea if this can be used only in links, images etc. etc. So I just wanted to add that for the sake of completeness.

Really it depends how you use it, how "static" the ID is. Is it site specific, user specific, can it change dynamically etc... ?

Sorry this was so long, I just recently wrote 4 wordpress plugins that add simple shortcodes ... lol. So I was just recently thinking about them. And I really love shortcodes.

Hope that helps.

ArtisticPhoenix
  • 20,683
  • 2
  • 18
  • 34
  • Closures are wonders :) but in my case where I have a long list of variables to be used, classes look promising to me. Thanks! :) – Mohit Sep 30 '18 at 06:02
  • Yea it really depends, if it's something site wide that cant be changed then a class is fine. If this were an affiliate program you run (instead of subscribe to) than setting it in the DB with a page in the admin area would make more sense as then users could change it etc... There wasn't enough info in the question for me to tell and like I said I just wrote like 4 plugins (this week) so it was still fresh in my mind.... lol ... It really depends how dynamic the affiliate ID is for you. – ArtisticPhoenix Sep 30 '18 at 06:08
  • I was just recently playing with re-setting the scope of a closures with [bindTo](http://php.net/manual/en/closure.bindto.php). Basically I have a callback class for my shutdown handler, and one of the callback takes a dynamic callback as a closure but I wanted it's scope to be in the callback class and not the closure. You can see it [HERE](https://github.com/ArtisticPhoenix/Shutdown/blob/master/src/evo/shutdown/callback/ShutdownDynamicCallback.php) on my GitHub stuff (in the __construct) – ArtisticPhoenix Sep 30 '18 at 06:11
  • The callbacks let you decide at runtime how you want to output/display errors ( such as JSON, HTML, TEXT, in a file, or even as an email). This project is not finished yet, but it's a really cool system I use (an earlier version ) in a lot of my code for error management and debugging. But basically the class has a `args` array that wouldn't be able to be accessed for a closure defined outside the class. So with bindto I can change the scope and everything works like the non-dynamic callbacks. :) – ArtisticPhoenix Sep 30 '18 at 06:16
  • I was actually looking for all the possibilities to such problem so that I could use the most appropriate one. however, the actual case was a bit more complex and big than what I represented in the question .. was feeling lazy after waking up that early on sunday ..lol .. but yeah, indeed closures are a perfect solution to such situations.. thanks man! – Mohit Sep 30 '18 at 06:17
  • 1
    Sure, I do a lot of coding so it's no big deal for me to show them all (even though I am lazy too ... lol). I've only been doing PHP since 2009 ... ha ha. – ArtisticPhoenix Sep 30 '18 at 06:19
  • I have only used `bind` in JS so far. This looks quite interesting and pushing me to dig further into this.. btw, I could install WordPress back then in 2009, but didn't know heck about PHP .. you have been doing this for a decade .. haha awesome! – Mohit Sep 30 '18 at 06:44
  • In JavaScript it more similar to `somefunction.apply( this, [ arguments ] )` Which allows you to change the scope of a callback (somefunction). Typically used in a plugin. Bind (as I am thinking about it, jQuery) is an event registration, which is very diffrent. These in both cases change the scope of `this` to be where that is done (bindTo, or apply) instead of where the callback is defined. So in the PHP example I could define the closure outside of the class (I posted from github) but because the scope is bound then `$this` would reference that class and not whatever it was defined as – ArtisticPhoenix Sep 30 '18 at 06:51
  • Great answer @ArtisticPhoenix. Just bear in mind woocommerce and wordpress both use globals a lot, so it's not neccessarily a bad thing (if your global is a class). I prefer using a class myself as it eliminates the off (good?) chance of variables being used by other plugins. – Gavin Simpson Sep 30 '18 at 07:28
  • @GavinSimpson - Globals are always bad, word press uses them a lot because when it was originally built in PHP4 , OOP support was poor, and even to this day it's OOP support is somewhat lacking. Because of this, it's a convince mechanism because originally there was no public API ( such as defining interfaces would give). Just because they use it a lot doesn't mean it's correct. It just means they have to support legacy plugins and systems that would all have to be re-written to use a proper API. – ArtisticPhoenix Sep 30 '18 at 07:39
  • It can be exceptionally difficult to debug globals ( and I have spent days ) debugging code that used global. Almost all of that was before PHP 5.3. For the most part WP uses them now to pass around stuff like the DB connection that doen't change. In reality this should be a class, but that would require significant changes to third party code. Which is something they have no control over. – ArtisticPhoenix Sep 30 '18 at 07:42
  • Yes absolutlely. That's 100% why I stick to using classes. It's makes debugging, and adding stuff, that much simpler. – Gavin Simpson Sep 30 '18 at 07:56
  • https://stackoverflow.com/questions/1557787/are-global-variables-in-php-considered-bad-practice-if-so-why . I agree with Anthony Rutledge's answer. – Gavin Simpson Sep 30 '18 at 08:02