Taking PHP seriously. Learning PHP Using Output Modifiers with Parameters
A Soyuz rocket delivered by train to the launch pad. NASA public domain photo.
Virtues of PHP
PHP has some very deep and definitely true features.First, state. Every web request starts with a completely blank slate. Its namespace and global variables are uninitialized, with the exception of some standard global variables, functions, and classes that provide primitive functionality and life support. By starting each request from a known state, we gain a kind of isolation from possible errors; If request t encounters a software problem and fails, the bug has no effect on the execution of subsequent request t+1. In reality, of course, application state resides in other places besides the program heap, and it is quite possible to completely corrupt the database, memcache, or filesystem. But PHP shares this weakness with every conceivable framework that allows stateful persistence. At the same time, sharing software heaps between requests reduces the cost of most software bugs.
Second, parallelism. An individual request runs in one PHP thread. At first glance, this seems like a stupid restriction. But since our application runs in the context of a web server, we have a natural source of concurrency: web requests. By simply sending asynchronous requests to ourselves, we can easily achieve parallelism with isolated states and copy-restore calls. In practice, this is safer and more error-tolerant than the locking and shared state approach used in other general-purpose languages.
In conclusion, the fact that PHP programs operate at the request level means that the programmer's workflow is fast and efficient, and remains fast after the application changes. Plenty of productivity languages claim to do this, but unless they clear their state on every request and the main event thread shares program-level state between requests, they almost always take a while to run. For a typical Python application server, a typical debugging loop would look something like “think, edit, restart server, send some test requests.” Even if “restart server” only takes a few seconds out of the total number of hours, it takes a big chunk out of 15-30 seconds of our human brains on the need to keep the most unnecessary part of the current state in our heads.
I argue that "think, edit, reload" style of PHP development makes developers more productive. In long and complex project development cycles, this provides even greater gains.
The case against PHP
If all this is true, why is he hated so much? When you put all the colorful hyperbole aside, the main complaints about a PHP cluster boil down to the following problems:
Not to sound like an unreflective PHP apologist: these are all serious problems that make it more likely to introduce defects. They are unforced errors. There is no inherent trade-off between the Good Parts of PHP and these issues. It should be possible to create PHP that solves these shortcomings while retaining all the good aspects.
HHVM and Hack
This successor to the PHP system is called HackHack is what people call an "incremental type system" programming language for PHP. "Type system" means that it allows the programmer to make automatically verifiable invariants about the data that flows through the code: a given function takes a string or a number and returns a Fribbles sheet, like in Java or C++ or Haskell or any other statically typed language, whichever one you choose. "Gradual" means that some parts of your codebase may be statically typed, while other parts may still be in messy, dynamic PHP. The ability to combine these approaches allows large codebases to be migrated over time.
Instead of spilling a ton of ink here describing the Hack type system and how it works, just play around with it. I'll be here when you return.
It's a neat system, and it's quite ambitious in what it allows you to express. And having the ability to gradually migrate a project to Hack when it grows larger than you initially expected is a unique benefit of the PHP ecosystem. Hack type checks preserve a "think, edit, reload page" style workflow because they run in the background, gradually updating the codebase model when it sees modifications to the file system. The Hack Project provides integrations with all popular editors and IDEs, so you can see type error feedback as soon as you finish typing, just like in the web demo.
Let's look at the totality of the real risks that PHP creates in the light of Hack:
Hack provides an opportunity that other popular members of the MPDPL family do not have: the ability to introduce a type system after the main development, and only partially, in those parts of the system where the value outweighs the cost.
HHVM
Hack was originally developed as part of the HipHop Virtual Machine, or HHVM, an open-source virtual environment for PHP. HHVM provides another important option for a successful project: the ability to launch your site faster and more economically. FacebookFilters in Revolution allow you to manipulate how certain tags are processed. They allow you to change values right inside your templates.
Input filters
Currently, input filters are used in preparation for processing output filters. They are usually only used inside the MODX engine.
Output filters
In Revolution, output filters behave the same way as PHx in Evolution, only the filters are now built directly into the MODX engine. The syntax looks like this:
[]
Filters can be applied sequentially. To do this, write them in a row (from left to right):
[]
Filters can also be used to modify the output of snippets. The filter must be specified before all parameters (before the question mark):
[]
Output modifiers
The table shows some modifiers and examples of their use. In the examples, modifiers are applied to placeholders, but you should remember that they can be applied to any MODX tags. Make sure the tag you use outputs at least something that the modifier will process.
Conditional Output Modifiers
Modifier | Description | Usage example |
---|---|---|
if, input | Pass arbitrary text as input for the next modifier | [[*id:input=`[[+placeholder]]`:is=`1`:then=`Yes`:else=`No`]] |
or | OR | [[+numbooks:is=`5`:or:is=`6`:then=`There are 5 or 6 books here`:else=`Not sure how many books`]] |
and | Combining multiple modifiers with a link AND | [[+numbooks:gt=`5`:and:lt=`10`:then=`There are from 5 to 10 books here`:else=`There are either less than 5 or more than 10 books`]] |
isequalto, isequal, equalto, equals, is, eq | Compares the passed value with the set value. If the values match, the value “then” is displayed, if not - “else” | [[+numbooks:isequalto=`5`:then=`There are 5 books here`:else=`Not sure how many books`]] |
notequalto, notequals, isnt, isnot, neq, ne | Compares the passed value with the set value. If the values do NOT match, the value "then" is displayed, if not - "else" | [[+numbooks:notequalto=`5`:then=`Not sure how many books`:else=`There are 5 books`]] |
greater thanorequalto, equalorgreaterthen, ge, eg, isgte, gte | The same, only the condition is “Greater than or equal to” | [[+numbooks:gte=`5`:then=`There are 5 books or more here`:else=`There are less than five books here`]] |
isgreaterthan, greaterthan, isgt, gt | The same, only the condition “Strictly more” | [[+numbooks:gt=`5`:then=`There are more than five books here`:else=`There are 5 books or less`]] |
equaltoorlessthan, lessthanorequalto, el, le, islte, lte | The same, only the condition is “Less than or equal to” | [[+numbooks:lte=`5`:then=`There are 5 books or less here`:else=`There are more than five books here`]] |
islowerthan, islessthan, lowerthan, lessthan, islt, lt | The same, only the condition “Strictly less” | [[+numbooks:lte=`5`:then=`There are less than five books here`:else=`There are 5 books or more here`]] |
hide | Hides the element if the condition is true | [[+numbooks:lt=`1`:hide]] |
show | Displays an element if a condition is true | [[+numbooks:gt=`0`:show]] |
then | Used to create conditions | [[+numbooks:gt=`0`:then=`Books in stock!`]] |
else | Used to create conditions (together with “then”) | [[+numbooks:gt=`0`:then=`Books in stock!`:else=`Sorry, but everything is sold out.`]] |
memberof, ismember, mo | Checks whether the user is a member of the specified user group | [[!+modx.user.id:memberof=`Administrator`]] |
Modifiers for working with strings
Modifier | Description | Usage example |
---|---|---|
cat | Adds a value after the tag | [[+numbooks:cat=`books`]] |
lcase, lowercase, strtolower | Converts all letters to lowercase | [[+title:lcase]] |
ucase, uppercase, strtoupper | Converts all letters to uppercase | [[+headline:ucase]] |
ucwords | Capitalizes the first letter of words | [[+title:ucwords]] |
ucfirst | Capitalizes the first letter in a string | [[+name:ucfirst]] |
htmlent, htmlentities | Converts all characters to corresponding HTML entities | [[+email:htmlent]] |
esc, escape | Safely escapes characters using regular expressions and `str_replace()`. Also escapes MODX tags. | [[+email:escape]] |
strip | Replaces all hyphens, tabs, and any number of spaces with just one space | [[+textdocument:strip]] |
stripString | Cuts the specified substring from a string | [[+name:stripString=`Mr.`]] |
replace | Replaces substrings | [[+pagetitle:replace=`Mr.==Mrs.`]] |
striptags, stripTags, notags, strip_tags | Cuts out all tags (allowed tags can be specified). Do not use for safety purposes. | [[+code:strip_tags]] |
len,length,strlen | Prints the length of a string | [[+longstring:strlen]] |
reverse, strev | Reverses a string character by character | [[+mirrortext:reverse]] |
wordwrap | Inserts a line break after every nth character (words are not broken) | [[+bodytext:wordwrap=`80`]] |
wordwrapcut | Inserts a line break after every nth character, even if this character is inside a word | [[+bodytext:wordwrapcut=`80`]] |
limit | Prints a specified number of characters from the beginning of a line (default is 100) | [[+description:limit=`50`]] |
ellipsis | Adds an ellipsis and truncates the line if it is longer than the specified number of characters (default is 100) | [[+description:ellipsis=`50`]] |
tag | Shielding. Displays the element as it is, without the :tag. For use in documentation | [[+showThis:tag]] |
add, increment, incr | Adds the specified number (default +1) | [[+downloads:incr]] [[+blackjack:add=`21`]] |
subtract, decrement, decr | Subtracts the specified number (default -1) | [[+countdown:decr]] [[+moneys:subtract=`100`]] |
multiply, mpy | Multiplies by the specified number (default *2) | [[+trifecta:mpy=`3`]] |
divide,div | Divides by the specified number (default /2) | [[+rating:div=`4`]] |
modulus, mod | Returns the modulus of a number (default: %2, returns 0 or 1) | [[+number:mod]] |
ifempty,default,empty,isempty | Returns the modifier value if the tag value is empty | [[+name:default=`anonymous`]] |
notempty, !empty, ifnotempty, isnotempty | Returns the modifier value if the tag value Not empty | [[+name:notempty=`Hello [[+name]]!`]] |
nl2br | Replaces newlines \n with the HTML br tag | [[+textfile:nl2br]] |
date | Converts a timestamp to text according to the specified format (date format) | [[+birthyear:date=`%Y`]] |
strtotime | Converts date as text to UNIX timestamp | [[+thetime:strtotime]] |
fuzzydate | Accepts a timestamp and returns a date in the form "Today at 16:20 PM" | [[+createdon:fuzzydate]] |
ago | Returns the number of seconds, minutes, weeks, or months that have passed since the date specified in the tag. | [[+createdon:ago]] |
md5 | Creates an MD5 hash of a value | [[+password:md5]] |
cdata | Wraps the output with CDATA tags | [[+content:cdata]] |
userinfo | Returns the requested value from the user's profile. User ID required | [[!+modx.user.id:userinfo=`username`]] |
isloggedin | Returns 1 if the user is authorized in the current context | [[!+modx.user.id:isloggedin:is=`1`:then=`Yes`:else=`No`]] |
isnotloggedin | Returns 1 if user Not authorized in the current context | [[!+modx.user.id:isnotloggedin:is=`1`:then=`No`:else=`Yes`]] |
urlencode | Converts the value as a URL, that is, uses the PHP `urlencode()` function | [[+mystring:urlencode]] |
urldecode | Converts the value as from a URL, that is, uses the PHP function `urldecode()` | [[+myparam:urldecode]] |
Modifiers for working with users must be called uncached so that each user sees the latest data.
Using Output Modifiers with Parameters
If a tag has parameters, then they must be written immediately after the modifier:
[[!getResources:default=`Sorry, nothing found`? &tplFirst=`blogTpl` &parents=`2,3,4,8` &tvFilters=`blog_tags==%[[!tag:htmlent]]%` &includeTVs=`1` ]]
Creating a Custom Modifier
Any snippet can be used as an output modifier. To do this, simply specify the snippet name instead of the modifier. For example, let's create a snippet [] that adds a certain number of exclamation points to the output:
[[*pagetitle:makeExciting=`4`]]
Such a tag call will pass the following parameters to the makeExciting snippet for processing:
Parameter | Meaning | Example value |
---|
- Overloading class properties
- Method overloading
- Interfaces
Object interfaces allow the programmer to write code that specifies what methods and properties a class should include, without having to describe their functionality.
Interfaces are declared in the same way as regular classes, but using the keyword " interface"; method bodies of interfaces must be empty. To include an interface in a class, the programmer must use the keyword " implements" and describe the functionality of the methods listed in the included interface. If required, classes can include more than one interface by listing them separated by a space.
If a class includes an interface and does not describe the functionality of all methods of that interface, executing code using that class will fail with a fatal error indicating which methods were not described. Interface example:
interface ITemplate
{
public function setVariable ($name, $var);
public function getHtml ($template );
}Class Template implements ITemplate
{
private $vars = array();Public function setVariable ($name, $var)
{
$this -> vars [ $name ] = $var ;
}Public function getHtml ($template )
{
foreach($this -> vars as $name => $value ) (
$template = str_replace ("(" . $name . ")" , $value , $template );
}Return $template ;
}
}
?> - instanceof operator
Support for checking dependencies on other objects. The is_a() function, known from PHP 4, is no longer recommended.
if ($obj instance of Circle) (
print "$obj is a Circle" ;
}
?> - final method
The final keyword allows you to mark methods so that an inheriting class cannot overload them. By placing a keyword before declarations of methods or properties of a class final, you can prevent them from being overridden in child classes, for example:
class BaseClass(
public function test() (
echo "Method BaseClass::test() called\n";
}Final public function moreTesting() (
echo "BaseClass::moreTesting() method called\n";
}
}Class ChildClass extends BaseClass (
public function moreTesting() (
echo "ChildClass::moreTesting() method called\n"<<< НазадContent Forward >>> If you have any other questions or something is not clear - welcome to our
Access to object properties can be overloaded using methods __call, __get And __set. These methods will only fire if the object or inherited object does not contain the property being accessed. The syntax is:
void __set(string name, mixed value)
Void __get(mixed name)
With these methods, class property accesses can be overloaded to execute arbitrary code defined in the class. The name argument contains the name of the property being accessed. The value argument of the __set() method must contain the value that will be assigned to the class property named name.
__get And __set:
class Setter(
public $n ;
private $x = array("a" => 1 , "b" => 2 , "c" => 3 );
Function __get ($nm) (
print "Reading [$nm]\n" ;
If (isset($this -> x [ $nm ])) (
$r = $this -> x [ $nm ];
print "Received: $r\n" ;
return $r ;
) else (
print "Nothing!\n" ;
}
}
Function __set ($nm, $val) (
print "Write $val in [$nm]\n" ;
If (isset($this -> x [ $nm ])) (
$this -> x [ $nm ] = $val ;
print "OK!\n" ;
) else (
print "Everything is bad!\n" ;
}
}
}
$foo = new Setter();
$foo -> n = 1 ;
$foo -> a = 100 ;
$foo -> a++;
$foo -> z++;
var_dump($foo);
?>
The result of executing the considered script will be:
We write 100 in [a]
OK!
Read [a]
Received: 100
We write 101 in [a]
OK!
Read [z]
Nothing!
Write 1 in [z]
Everything is bad!
object(Setter)#1 (2) (
["n"]=>
int(1)
["x:private"]=>
array(3) (
["a"]=>
int(101)
["b"]=>
int(2)
["c"]=>
int(3)
}
}
Method calls can be overloaded using methods __call, __get And __set. These methods will only fire if the object or inherited object does not contain the method being accessed. Syntax:
mixed __call(string name, array arguments)
Using this method, class methods can be overloaded to execute arbitrary code defined in the class. The name argument contains the name of the called method. The arguments that were passed to the method when called will be returned via arguments. The value returned by the method __call(), will be passed to the caller.
Example of overload using __call:
class Caller(
private $x = array(1, 2, 3);
Function __call ($m, $a) (
print "Method $m:\n called";
var_dump($a);
return $this -> x ;
}
}
$foo = new Caller();
$a = $foo -> test (1 , "2" , 3.4 , true );
var_dump($a);
?>
The result of running the example considered:
The test method is called:
array(4) (
=>
int(1)
=>
string(1) "2"
=>
float(3.4)
=>
bool(true)
}
array(3) (
=>
int(1)
=>
int(2)
=>
int(3)
}