Developer Blog

Just yesterday I finished the last test for the new relationship type for PHPixie ORM - Nested Set. I thought whether to implement this or the Closure Table approach for storing trees in SQL databases. But the latter has a huge downside of the quasiquadratic size of the relationship table in the worst case (just 20 nodes can result in 190 records stored there). So the next step was to try improving the classic Nested Set approach.

The biggest problem with it is the high cost of node manipulation. The more to the left of the tree we insert a new node the more records need to be updated. For example:

Nested Set

Let’s say we need to add a Fay subcategory to Pixies, this will result in the following:

Nested Set Insertion

As we see all the records in the table need to be updated. Now imagine you use this approach to save a tree of comments to your articles. Every time somebody adds a new comment you update a whole bunch of records, furthermore you have to make sure this is done within a transaction and preferably on a SERIALIZABLE level. Otherwise even just a few active users commenting at the same time can break the entire index. And of course the performance also suffers greatly as a result.

I have found an elegant way that fixes this problem by slightly modifying the classic Nested Set. The idea is simple although the implementation does become somewhat more difficult. Every node should also store the identifier of the subtree they belong to, for example the id field of the root node. Also in each subtree we enumerate the left and right indexes separately. This way the above example would look like this:

Optimized

When inserting we only need to update the nodes in a single subtree:

Optimized Insertion

As we see the Plants subtree didn’t change at all this time. In practice this greatly reduces the amount of updated rows. If the changes are made in different subtrees they don’t interfere with each other at all, and even if one subtree gets corrupted it won’t affect the others.

As a downside the code becomes more complex. It’s easy to make a mistake and forget to update the root field when moving a node from one subtree to the other, harder to construct the object tree from such records, etc. That’s actually why so much time was spent on writing tests.

Usage with PHPixie ORM

The full doc on using Nested Set is coming soon, but for now here’s a quick guide for those who want to try it out now.

First add 4 integer fields to your table: left, right, rootId and depth (of course the field names are fully configurable, and those are just the defaults). Then add the following to /bundes/app/assets/config/orm.php:

1
2
3
4
5
6
7
8
9
return array(
     'relationships' => array(
          //....
         array(
             'model' => 'category' //model name,
             'type' => 'nestedSet'
         )
     )
);

Then it’s straightforward:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$fairies->children->add($sprites);
$fairies->children->add($pixies);

//or like this
$pixies->parent->set($fairies);

//move a category to root
$pixies->parent->remove();

//load the entire tree from database
//note the ->where('depth', 0) condition,
//we use it to start only from the top-level nodes
$categories = $orm->query('category')
    ->where('depth', 0)
    ->find(array('children'));

By the way PHPixie will itself update the indexes when you delete entities, and prevent you from deleting entities if they would leave undeleted children.

I hope you’ll like this new feature, and even if you don’t use PHPixie may find this approach to Nested Set useful.

If you’re interested in implementation details or have any question just drop a message in our chatroom.

on 24 April 2016

The Standard PHP Library (SPL) contains a lot of useful tools that sometimes go unnoticed by developers. This is going to be a series of videos focusing on useful components it provides. At first we will look at the included classic datastructures like the Doubly Linked List, Stack, Queue, Heap and the SplObjectStorage.

on 8 February 2016

While not directly connected to web development Neural Networks and Machine Learning in general are exciting subjects that can often come useful, especially for tasks like spam detection and image recognition. Thanks to the excellent FANN extension that abstracts out all the math behind the scenes we can easily use them with PHP. This is a small intro into the training algorithm and a walkthrough though training a simple network to do the XOR operation.

on 3 December 2015

A few days ago I switched a server hosting a bunch of my personal websites to PHP 7. Some of those rather old and spanning a wide range of frameworks and CMSes. These are some of my thoughts for those debating whether they should switch to PHP 7 or wait some more.

Firstly, I know there are people who don’t consider a stable release truly “stable” until it has been out for a while, waiting for errors to be found and patched. And from what I’ve seen so far, trying out every release candidate as it was released, it is definitely safe to start using PHP 7 as soon as it is out. I never had an unexpected behavior, a random segmentation fault, or anything that was not my fault. Even though it is a major version it does not cause much backwards compatibility breaks, so in general terms you can treat is just as if it was PHP 5.7, just significantly faster.

The performance does shine, almost unbelievably so. To give you an example a simple PHPixie app performed up to 3 times faster, almost matching the speed of Phalcon on PHP 5.6. A recent report by Google claimed that even a 10% decrease of performance results in a hefty percentage of lost customers. Following this logic if you manage to double your performance just by upgrading your PHP, you get more sales for free. Mention this to your manager when trying to convince them to switch. Nothing persuades better than sales numbers.

Some notes

The mysql extension is not available anymore, so if you never switched to PDO or mysqli this is going to be a problem. Fortunately a simple replace of mysql_ function calls with mysqli_ solves this perfectly.

E_STRICT errors have been reclassified to other error types. So if you had those errors disabled they will start popping out as warnings and notices. For example calling non-static methods statically is now E_DEPRECATED and caused me a lot of troubles with Joomla 2.5 which for some reasons does this in quite a few places. Also signature mismatch during inheritance will yield a warning. Surprisingly all of the Wordpress sites with quite a lot of plugins installed worked pretty well on the first try, the functional nature of Wordpress made it resilient to E_STRICT changes entirely.

foreach operates on a copy of the array. So if you modify the array while iterating over it, those changes will not be available inside that loop anymore.

Another troublesome change is that $foo->$bar['baz'] is now interpreted as ($foo->$bar)['baz'] unlike the PHP 5 $foo->{$bar['baz']}. This is a weird case, but I ran into it a few times in some Wordpress plugins.

Keep in mind that not all your PHP extensions may support PHP 7 yet, sadly I can no longer use the excellent XCache opcode cacher that worked perfectly for me for so many years.

Other than that the breaking changes were rather tame and you are very unlikely to run into them. If you want to be thorough a full list is available here.

All in all it took me about 5 hours to update all my websites to PHP 7. The process is really straightforward and packages are already available for all major Linux distributions, so there is really no excuse not to upgrade.

on 17 November 2015

REPL tools (Read-Eval-Print Loop) are great interactive shells for learning programming languages and playing with new libraries. If you ever used the console in your browsers developer tools, that is exactly what this is. In this video we’ll take a look at the two most popular REPL environments for PHP: PsySh and Boris, and try using them with PHPixie components.

on 1 November 2015

Just yesterday, after almost 4 months of being done, PHPixie 3 got its first stable release. From now on the releases will follow in a SemVer-ish fashion. The version will be in a 3.X.Y format, with Y increasing with minor changes such as documentation updates or bug fixes, and X increasing every time the API changes.

This release was made possible due to the immense amount of help I got from people reporting bugs, finding discrepancies in the documentation and sending in pull requests. That got me thinking that concentrating on just the software when I started development was huge mistake. I was under the impression that as soon as the code is good enough the community is going to build itself. Now I see that an opensource project consists as much of the community as it does of code, and that part has to be actively built too.

Good community keeps people in stronger than good software and can easily surpass it. For me one such example is the Gentoo IRC channel, even when I was on a different Linux distro I would still hang around the Gentoo IRC, just because I liked it there. An even better community is the NetHack channel, asking real people questions about in-game items is far more enjoyable than just consulting the wiki. In that it remains the best multiplayer-ish experience I’ve ever had with a game. And now I want PHPixie to become something similar.

The first step, and I haven’t seen that done before in the PHP world, was including a link to our chatroom in the error template. So every time an exception jumps out you know exactly where to start looking for help. I’m also pondering the idea of getting rid of the forum entirely as Github issues fulfil that functionality much better. The only thing holding me back is the possibility of alienting users without a Github account.

All of the websites content, apart from the blog posts, is now freely editable, e.g. component documentation pages are generated from the README.md files in their repositories. This makes the website easily translatable to any other language and also usable with any static site generator.

The blog is going to get a new post at least once per week, and since I have finally figured out how to do proper video recording I plan to include more video tutorials on general PHP stuff. I also started actively submitting my talks to conferences, and hopefully this will help us to further grow in numbers.

Please let me know if you have any ideas for creating a better environment for the community or some other helpful advice.

P.S. There are still PHPixie stickers to give away that I can ship worldwide, so tell me if you want one.

on 11 October 2015

Using middleware for incremental request processing is getting increasingly popular, especially now that we have a common PSR-7 interface to support it. But it has brought out an important issue that made me rethink the very foundation of how requests are handled in what we used to call MVC.

A frequent problem that a lot of middleware users run into is passing some data along with the request to the next processor in the chain. All frameworks and even the PSR-7 use request attributes to achieve this, e.g. how routing parameters are stored in the request. This works great for simple things like strings, but it gets unweildy as soon as you try it with something more complex. For example I’ve see authentication middlewares that on successfull login put the user object into request attributes to pass along. This I feel totallly breaks the SRP, since an HTTP request should really have no idea about things like the user domain.

A quick solution for this would be to use a some sort of a Context object instead of a Request to pass along the middlewares. This Context would contain the HTTP request, authorization information, etc.

1
2
3
4
5
6
7
// Instead of this:
$id   = $request->getAttribute('id');
$user = $request->getAttribute('user');

// We would have
$id = $context->httpRequest()->getAttribute('id');
$user = $context->auth()->user();

Such a context object encapsulates all the variable data that a certain middleware would require for processing. Actually this is very similar to how PHPixie ealready handles request related data, just taking it one step further.

Another important thing is that frameworks are used to passing requests to actions, or in some cases just the routing parameters, e.g.:

1
2
3
4
5
6
7
8
9
10
11
public function someAction($request)
{

}

//or 
public function someAction($id)
// Where $id is a routing attribute
{

}

In those cases if a controller needs to access user authorization data, it has to get it from somewhere else, usually some authorization component:

1
2
3
4
5
public function someAction($id)
{
    $user = $this->authService()->user();
    //...
}

In this case the $user is actually a “hidden” parameter that is received from an outside scope. Now imagine we used a Context for it too, passing everything as a parameter:

1
2
3
4
5
public function someAction($context)
{
    $id   = $context->httpRequest()->getAttribute('id');
    $user = $context->auth()->user();;
}

This would make unit testing so much easier, since all parameters are passed to the method, instead of relying on mocking internal services. This would also pave the way to transition PHP frameworks from being “web frameworks” to being “application frameworks” by making HTTP just an optional part of the context instead of the main core of processing logic.

The Context can be made pluggable, just look at these interfaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
interface AuthContext
{
    public function user();
}

interface AuthContextContainer
{
    /**
     * @return AuthContext
     */
    public function authContext();
}

interface HTTPContext
{
    /**
     * @return \Psr\Http\Message\RequestInterface
     */
    public function request();

    // maybe more methods for cookie handler etc
}

interface HTTPContextContainer
{
    /**
     * @return HTTPContext
     */
    public function httpContext();
}

// the concrete Container
// declares which contexts it supports
// by implementing required interfaces
class Context implements AuthContextContainer, HTTPContextContainer
{
    //...
}

Passing such contexts to middlewares and controllers would be a much better OOP approach to passing required parameters and would also work for non-HTTP environments.

Since I already have some experience implementing contexts in such a way for PHPixie, I was thinking perhaps there is some interest for a PSR for this. If so I would love to start working on it. Would really love to hear your thoughts on this.

on 22 September 2015

There seems to be some confusion about the wrapper approach to extending ORM models, so let’s take a closer look at it. The usual approach in ActiveRecord style ORMs is to extend some kind of a base model class and add your functionality to it, this tightly couples the ORM logic with your application logic, and makes the resulting class hard to test. Such coupling is usually solved by the Decorator pattern which is the one used in this case. Let’s take a look at a simplified example:

Imagine we have a typical ActiveRecord model that allows us to get an item by its id field and a User class extending it:

1
2
3
4
5
6
7
8
9
10
11
12
class Model
{
    public function getById($id)
    {
        //
    }
}

class User extends Model
{

}

Now we would like to add a special method to the User class that checks if the user id belongs to an administrator, depending on some field from the database, something like this:

1
2
3
4
5
6
7
8
class User extends Model
{
    public function isAdmin($id)
    {
        $user = $this->getById($id);
        return $user->role === 'administrator';
    }
}

This way the isAdmin method is relying on the database logic contained in the Model class, and even though the actual check we do is trivial, testing such a method is becoming slightly more complicated already.

Another problem to consider is that all models must extend the base Model class. Imagine the following setup:

1
2
3
4
5
6
7
8
9
class Item
{
    //some logic here
}

class Product extends Item
{

}

Now what if you would like to save Products using the ORM, but you don’t want to use ORM for Items that are not Products? Since Product needs to extend the Model class to be usable with ORM you will have to make Item extend it to in order for this to work. Doctrine solves this problem by getting rid of the base class, and moving the whole persistence layer outside.

What if the Model and User classes need different constructor parameters? You’ll have too keep track of those in your User class too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Model
{
    // some dependencies
    public function __construct($database, $orm)
    {
        $this->database = $database;
        $this->orm = $orm;
    }
}

class User
{
    public function __construct($database, $orm, $auth)
    {
        parent::__construct($database, $orm);
        $this->auth = $auth;
    }
}

Solving this all with wrappers

Fortunately some OOP can help us solve all of these issues.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// first we add a Model interface
interface ModelInterface
{
    public function getById($id);
}

// and make the model implement it
class Model implements ModelInterface
{
    public function __construct($database, $orm)
    {
        $this->database = $database;
        $this->orm = $orm;
    }

    public function getById($id)
    {
        //
    }
}

// A wrapper also implements the interface.
// It acts as a proxy passing all method calls
// to an actual Model passd in its constructor
class ModelWrapper implements ModelInterface
{
    protected $model;

    public function __construct(ModelInterface $model)
    {
        $this->model = $model;
    }

    public function getById($id)
    {
        //method calls are proxied
        //to the underlying model
        return $this->model->getById($id);
    }
}

// The User class can now extend the wrapper instead
class User extends ModelWrapper
{
    // We now only need the actual
    // user realted dependencies
    // and the model to proxy calls to
    public function __construct(ModelInterface $model, $auth)
    {
        parent::__construct($model);
        $this->auth = $auth;
    }

    // This method doesn't need to change
    // since the getById() is proxied
    public function isAdmin($id)
    {
        $user = $this->getById($id);
        return $user->role === 'administrator';
    }
}

// We would instantiate it like this:
$model = new Model($database, $orm);
$user = new User($model, $auth);

This might look somewhat more complex to the usual approach, but the code the end user cares about, the actual User class did not suffer much modifications. But it did get a lot of flexibility. We can now easily test it by supplying a mocked Model instance as a parameter, this way the code behind the User class is no longer tied to anything related to the database allowing us test it as an any other simple class.

1
$user = new User($mockedModel, $auth);

We can now also resolve the issue of having the Item and Product classes by adding proxy methods to the Prodcut class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Item
{
    //some logic here
}

// This still required copy pasting
// proxy methods, but unlike pasting
// methods directly from the Model class
// we don't have to worry about updating
// the Product class when the Model class changes
class Product extends Item implements ModelInterface
{
    protected $model;

    public function __construct(ModelInterface $model)
    {
        $this->model = $model;
    }

    public function getById($id)
    {
        return $this->model->getById($id);
    }
}

The main reason why such an approach was taken with the PHPixie ORM is that Entities, Repositories and Queries require constructor dependencies that user classes don’t really need to know about. Using wrappers allows to bring the actual instantiation to the user (the ORMWrappers class in your project) while still hiding those parameters. So if at some point the ORM component gets refactored and the implementation changes, your wrappers should still work perfectly well with it.

on 15 August 2015

ReactPHP is an event-driven socket server written in PHP that is meant to run peristently unlike the standard Apache/Nginx approach where each request is handled by a separate process. This means it only needs to bootstrap your code once and then continuously feed it requests. It cuts away all the boilerplate of autoloading classes, initializing the framework, reading config files etc. every time.

The limitation here is that the developer has to keep in mind that the process and all services are going to be reused, so any kind of global or static scope has to be avoided as much as possible. This makes it hard to use it with frameworks that were not especially designed for it.

Luckily PHPixie already avoids global scope and an initialized framework can easily be reused. Here is all you need to run a PHPixie project with React:

1
2
# Add ReactPHP with composer
php ~/composer.phar require react/react

Create a react.php file in your projects root folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

require_once('vendor/autoload.php');

$host = 'localhost';
$port = 1337;

$framework = new Project\Framework();
$framework->registerDebugHandlers(false, false);

$app = function ($request, $response) use ($framework, $host, $port) {
    $http = $framework->builder()->components()->http();

    //Build request URI
    $uri = 'http://'.$host.':'.$port.$request->getPath();
    $uri = $http->messages()->uri($uri);

    //Build a PSR7 ServerRequest
    $serverRequest = $http->messages()->serverRequest(
        $request->getHttpVersion(),
        $request->getHeaders(),
        '',
        $request->getMethod(),
        $uri,
        array(),
        $request->getQuery(),
        array(),
        array(),
        array()
    );

    // Process the request
    $frameworkResponse = $framework
        ->processHttpServerRequest($serverRequest);

    // Output response
    $response->writeHead(
        $frameworkResponse->getStatusCode(),
        $frameworkResponse->getHeaders()
    );
    $response->end(
        $frameworkResponse->getBody()
    );
};

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket);

$http->on('request', $app);

$socket->listen($port);
$loop->run();
1
2
# Run it
php react.php

Now visit http://localhost:1337/ in your browser and you should see PHPixie running under React. A simple benchmark reported about an 800% performance increase, which is a rather obvious result considering that each request now executes a far less amount a code. ReactPHP supports multiple event backends, I ended up using the one named simply event.

There are some limitations to using it though. You still need a web server for your static files. There also seems to be an issue with getting POST data from the request, React does not provide an easy way to get those, although there is a workaround for it.

Having a persistent runtime also allows for some interesting use cases, such as chat clients that don’t require any kinf of persistence, etc. If the idea of using PHPixie from within React looks interesting to the community I might release a sperate component to allow a wider support for it and an easier setup.

on 12 August 2015

Build Status Test Coverage Code Climate HHVM Status
Author Source Code Software License

PHPixie Debug was created to improve PHP development in any environment. Of course if you are already using a web framework debugging tools are already provided, but when developing a library, solving a programming puzzle or even using WordPress the lack of a debugging toolset is hindering. Even basic functionality like convertieng errors to exceptions requires registering a special handler. PHPixie Debug can bootstrap you with a convenient environment in just two lines of code.

Exceptions and tracing

The Debug library tries to achieve the same level of usage ina console environment as we already have in web applications. When writing libraries for PHPixie I often wanted to have exception traces that would include the part of code that the exception happened in. Another problem with traces in php is that calling print_r(debug_backtrace()) directly can quickly result in a wall of text if any argument in the backtrace was an object with some dependencies. Using debug_print_backtrace() gives a better result, but still prints all array members and requires output buffering to assign the result to a variable. Let’s take a look at the PHPixie trace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
require_once('vendor/autoload.php');
$debug = new \PHPixie\Debug();

try{
    throw new \Exception("test");

}catch(\Exception $e) {
    //Pretty print an exception
    $debug->exceptionMessage($e);
}

echo "\n-------\n";

//Automatic exception printing
//Will also display any logged messages
//(more on that later)
$debug->registerHandlers();

class Test
{
    public function a($string)
    {
        $array = array(1, 2);
        $this->b($string, $array);
    }

    public function b($string, $array)
    {
        $object = (object) array('t' => 1);
        $this->c($string, $array, $object);
    }

    public function c()
    {
        substr();
    }
}

$test = new Test();
$test->a("pixie");

Results in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Exception: test                                                       

5                                  
6 try{                                                                 
> throw new \Exception("test");                                    
8                                                                      
9 }catch(\Exception $e) {                                              

#0 D:\debug\examples\exceptions.php:7                                

-------                                                               

ErrorException: substr() expects at least 2 parameters, 0 given       

36 public function c()                                             
37 {                                                               
>> substr();                                                   
39 }                                                               
40 }                                                                   

#0 D:\debug\examples\exceptions.php:38                               
#1 D:\debug\examples\exceptions.php:38                               
    substr()                                                          
#2 D:\debug\examples\exceptions.php:33                               
    Test->c('pixie', array[2], stdClass)                              
#3 D:\debug\examples\exceptions.php:27                               
    Test->b('pixie', array[2])                                        
#4 D:\debug\examples\exceptions.php:43                               
    Test->a('pixie')                                                  

Logged items:                                                         

Note that the trace doesn’t include the handler that converted a PHP error into an exception, a lot of similar libraries forget to hide that part thus littering your trace. PHPixie Debug hides any of its handles for the traces.

Dumping variables
Dumping data can be done via a static \PHPixie\Debug::dump(), this is by the way the first PHPixie static method ever. The reason for such approach is that usually you delete such calls after you fix the issue, so the Debug library itself is never really a dependency of your application, thus massing it via DI is needless. But the static call will only work if the Debug library has been prior initialized, and it acts as a proxy to that instance. PHPixie will never have any actual static logic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
require_once('vendor/autoload.php');

use PHPixie\Debug;
$debug = new Debug();

Debug::dump("Array dump:");
Debug::dump(array(1));

Debug::dump("Short array dump:");
//Короткий дамп выводит минимум информации. 
//Это удобно например для вывода количества
//элементов массива или имя класса объекта
Debug::dump(array(1), true);

$object = (object) array('t' => 1);
Debug::dump("Object dump:");
Debug::dump($object);

Debug::dump("Short object dump:");
Debug::dump($object, true);

Debug::dump("Dump trace with parameters");
class Test
{
    public function a($string)
    {
        $array = array(1, 2);
        $this->b($string, $array);
    }

    public function b($string, $array)
    {
        $object = (object) array('t' => 1);
        $this->c($string, $array, $object);
    }

    public function c()
    {
        Debug::trace();
    }
}

$t = new Test();
$t->a("test");

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'Array dump:'

Array
(
    [0] => 1
)


'Short array dump:'

array[1]

'Object dump:'

stdClass Object
(
    [t] => 1
)


'Short object dump:'

stdClass

'Dump trace with parameters'

#0 D:\debug\examples\dumping.php:37
    PHPixie\Debug::trace()
#1 D:\debug\examples\dumping.php:32
    Test->c('test', array[2], stdClass)
#2 D:\debug\examples\dumping.php:26
    Test->b('test', array[2])
#3 D:\debug\examples\dumping.php:42
    Test->a('test')

Logging
To separate actual program output from debugging output usually developers store messages in some sort of array that they print afterwards. The problem with that approach is that if an exception happens or exit() is called those messages will not be printed. PHPixie debug always prints the log on exception and can register a handler to also do that whenever the script ends execution. Here are two examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use PHPixie\Debug;
$debug = new Debug();

Debug::log("test");
Debug::log(array(3));

class Test
{
    public function a($string, $num)
    {
        Debug::logTrace();
    }
}
$t = new Test();
$t->a("test", 5);

//Ручной вывод сообщений
$debug->dumpLog();
``````php?start_inline=1
Logged items:

[0] D:\debug\examples\logging.php:7
'test'

[1] D:\debug\examples\logging.php:8
Array
(
    [0] => 3
)


[2] D:\debug\examples\logging.php:16
#0 D:\debug\examples\logging.php:16
    PHPixie\Debug::logTrace()
#1 D:\debug\examples\logging.php:20
    Test->a('test', 5)

And with automatic logging:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

use PHPixie\Debug;
$debug = new Debug();

//Передав 'true' при регистрации хендлеров
//мы также включаем вывод лога в конце
//исполнения
$debug->registerHandlers(true);

Debug::log("test");

echo("Logged messages will be printed below");
``````php?start_inline=1
Logged messages will be printed now

Logged items:

#0 D:\debug\examples\log_handler.php:13
'test'

In conclusion
The main purpose of PHPixie Debug is not actually exception handling and tracing, it was designed to provide an OOP interface to PHP traces and variable dumping. This will in near future allow for the creation of a web debugged for PHPixie 3, all that is lacking is a nice web template for it. Its primary use as a standalone tool is to bootstrap your development environment in two lines of code and no additional dependencies. I hope next time when you’ll be solving a test puzzle for an interview or you’ll find yourself in need of some tracing in WordPress you’ll remember this little library and save some time of reading through debug_backtrace() output.

Demo
To try out PHPixie debug all you need to do is this:

1
2
3
4
5
6
7
8
9
10
11
git clone https://github.com/phpixie/debug
cd debug/examples

#если у вас еще нет Композера
curl -sS https://getcomposer.org/installer | php

php composer.phar install
php exceptions.php
php logging.php
php log_handler.php
php dumping.php

As with all the other PHPixie libraries the code is 100% unit tested and works with all versions of PHP 5.3+ (including nightly PHP7 and HHVM).

on 10 May 2015