Debugging PHP applications with PhpStorm
Friday, February 21. 2014
Anybody who has reached a certain level of skill in software development eventually will start thinking about how to make the job easier or be more productive during the day. With computers and programming, the tools you're using is a good starting point.
I've written code with a number of text editors (including vim and notepad), some of them really bad and some of them more suited to the task at hand. IMHO one of the best editors for a software developer is Microsoft's Visual Studio. Since many of my projects involve PHP, that is not the optimal editor for that. For years I used Notepad++, but then I got a recommendation for JetBrains PhpStorm. Since that it has become my weapon of choice for PHP.
For example, debugging the application is very simple operation. PhpStorm has all the client-stuff built into it, the only thing that is needed is Xdebug-extension to the server end. In my case, my web server is on a Linux and I typically work on a Windows 7. The debugging is done over my LAN. There is a very good article on PhpStorm author's web site with name Zero-configuration Web Application Debugging with Xdebug and PhpStorm. I don't think the zero-configuration part is true, but it is almost zero anyway.
On the server end I have a PHP-fpm running, it has
php_admin_flag[xdebug.remote_enable] = on
php_admin_value[xdebug.remote_host] = my_machine_name
php_admin_value[xdebug.remote_port] = 9000
In the worker configuration. That enables the server side to initiate a DBGP-connection to the given client (PhpStorm) and start sending data there. There reference for all the configuration directives can be found from Xdebug's docs.
On the PhpStorm end all you have to do, is enable the listener:
Also I had to drill a hole into my Windows 7 firewall, to allow my web server to connect into TCP/9000. If you're running a single machine setup, that won't be necessary.
Any settings for the setup can be found in the PhpStorm project:
But I think they're ok as a default. At least I didn't change anything there. If your debugger does not work, you fumbled the settings or your listener is not enabled. On Windows, run this on command prompt to confirm:
PS J:\> netstat -ano
Active Connections
Proto Local Address Foreign Address State PID
TCP 0.0.0.0:9000 0.0.0.0:0 LISTENING ?
It will return a long list of things, but one of the entries must be similar to that.
To make the choice between debugging and not debugging can be made really easy. There are a number of ways of starting a debug session, but for Firefox users there is an excellent add-on The easiest Xdebug. It will add an icon to your add-on bar:
If the bug is green, it will send a special cookie to the server during a page load request:
Cookie: XDEBUG_SESSION=netbeans-xdebug
That will initiate the debugging session for you and PhpStorm will stop on a breakpoint if one is set.
Switching to PhpStorm vastly improved my productivity. I would imagaine, that it will do that for anybody.
Zend Framework 2: Abusing ZeDb to manage multiple connections
Tuesday, February 18. 2014
My favorite way of approaching DAL in ZF2 is ZeDb. It uses lot of existing functionality, but adds a nice layer of its own stuff, and that stuff is really good. However, ZeDb has one single flaw, it does not support multiple database connections. The typical thinking is, that who an earth would want that. I do. I want other weird things too, but in an everyday real application, you simply need more connections than one.
I came up with an simple solution by abusing ZF2's ServiceManager. Typically you gain access to a model is via something like this:
$manager = $this->getServiceLocator()->get('ZeDbManager');
$model = $manager->get('Application\Entity\Prices');
To get that working you need to do the config in module.config.php, declare ZeDb's DatabaseManagerFactory and override Zend\Db\Adapter\Adapter with AdapterFactory and finally declare your DB-configuration and list the models. Its all in the ZeDb docs.
My idea exploits all three of those. I'll instantiate multiple DatabaseManagers via ServiceManager. Also, every model will select an existing DB-adapter for itself. To get that working there will be separate configuration for all of the connections.
Example module configuration:
return array(
'service_manager' => array(
'factories' => array(
'ZeDbManager' => 'ZeDb\Service\DatabaseManagerFactory',
'Zend\Db\Adapter\Adapter' => 'ZeDb\Service\AdapterFactory',
)
),
'zedb_db_in' => array(
'adapter' => array(
'driver' => 'pdo_pgsql',
),
'models' => array(
'Application\Model\Int\Products' => array(
'tableName' => 'products',
'entityClass' => 'Application\Entity\Int\Products',
),
),
),
'zedb_db_out' => array(
'adapter' => array(
'driver' => 'pdo_mysql',
),
'models' => array(
'Application\Model\Internal\Customers' => array(
'tableName' => 'customers',
'entityClass' => 'Application\Entity\Internal\Customers',
),
),
),
Here I declare the obligatory parts, and two separate configurations with names zedb_db_in and zedb_db_out.
I'll have to prime both the DatabaseManagers and Adapters in Module's onBootstrap(). After the objects exist, they are made accessible with:
public function getServiceConfig()
{
return array(
'factories' => array(
'zedb_db_in' => function (ServiceLocatorInterface $sm) {
return Module::getInDbManager();
},
'ZeDbAdapter_in' => function (ServiceLocatorInterface $sm) {
return Module::getInDbAdapter();
},
),
);
}
The model has to do some heavy lifting to get rid of the default Adapter:
class Products extends ZeDb\Model
{
public function __construct(Adapter $adapter, $options = null)
{
$adapter = $this->getDatabaseManager()
->getServiceLocator()
->get('ZeDbAdapter_in');
parent::__construct($adapter, $options);
}public function getDatabaseManager()
{
return Module::getInternalDbManager();
}
Now the setup is ready. In an action you can simply do:
$inDbManager = $this->getServiceLocator()->get('zedb_db_in');
$productsModel = $inDbManager->get('Application\Entity\Int\Products');
$products = $productsModel->SearchProductByCode('ABC123', null);
There is no limit on how many DB-connections you can have. Note that, the ZeDb-module bootstrap will initialize the default manager and adapter, but the idea is not to use them at all. Anyway, I'll stick with this until something better comes.
Zend Framework 2: Disable layout
Thursday, February 13. 2014
This topic pops up every once in a while. As default every rendered page implicitly renders a layout too. This is especially bad for JSON or XML or binary responses your application may generate as a response. So far the best explanation and some helpful insights is in Mr. Chris Schreiber's blog scribles.com in the article Disabling Layout in Zend Framework 2.
I was working on a web application, which was a machine-to-machine app and not meant for humans at all. So a layout is completely unnecessary. As Chris instructs, I went directly to the module-class and copy/pasted his code. It failed. He has some sort of typo in the code:
$sharedEventManager->attach(
'Zend\Mvc\Controller\AbstractController',
function(\Zend\Mvc\MvcEvent $event) {
When I was looking for SharedEventManagerInterface PHP-code, it says:
/**
* Attach a listener to an event
*
* @param string|array $id Identifier(s) for event emitting
* @param string $event
* @param callable $callback PHP Callback
* @param int $priority Priority at which listener should
* @return void
*/
public function attach($id, $event, $callback, $priority = 1);
There clearly are 3 obligatory parameters. Chris' code has three parameters with the optional priority. Something is missing. This is the fix:
$sharedEventManager->attach(
MvcEvent::EVENT_DISPATCH,
'Zend\Mvc\Controller\AbstractController',
function(\Zend\Mvc\MvcEvent $event) {
Now it works! However, as my application was in its early stages, I was missing the default controller with the class name of IndexController. Adding the code into onBootstrap() didn't help. None of the callback's code was run during event dispatch. More debugging revealed, that my code never triggered the MvcEvent::EVENT_DISPATCH. It did trigger a MvcEvent::EVENT_DISPATCH_ERROR instead. The reason is obvious, I didn't have the class.
For clarity I'll copy/paste my onBootstrap() entirely here:
public function onBootstrap(MvcEvent $evt)
{
$eventManager = $evt->getApplication()->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
// Make sure layout is not rendered for regular pages
$sharedEventManager->attach('Zend\Mvc\Controller\AbstractController',
MvcEvent::EVENT_DISPATCH,
function (MvcEvent $event) {
$dispatchResult = $event->getResult();
if ($dispatchResult instanceof ViewModel) {
$dispatchResult->setTerminal(true);
}
}, -99
);
// Make sure layout is not rendered for HTTP/404 pages
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR,
function (MvcEvent $event) {
$dispatchResult = $event->getResult();
if ($dispatchResult instanceof ViewModel) {
$dispatchResult->setTerminal(true);
}
}, -99
);
}
Now both events are handled properly, without attempting to find a layout. To test my code, I added a layout/layout.phtml to the project and commented out my above code. It did renader the file contents. Then I removed the comments and deleted the layout/-directory completely. My code still ran on both occasions: when action can be found and when action cannot be found. Actually I also have a CLI-interface to the app, but that won't render the layout anyway.
This is yet another example of the complexity of ZF2. Any trivial task turns out to be a huge pain in the butt. I don't want to start ranting about using PhpRenderer in CLI-app, that's a completely another complaint.
Managing PostgreSQL 8.x permissions to limit application user's access
Wednesday, February 5. 2014
I was working with a legacy project with PostgreSQL 8 installation. A typical software developer simply does not care about DBA enough to think more than once about the permissions setup. The thinking is that for the purpose of writing lines of working code which executes really nice SQL-queries a user with lots of power in its sleeves is a good thing. This is something I bump into a lot. It would be a nice eye-opener if every coder would had to investigate a server which has been cracked into once or twice in the early programming career. I'm sure that would improve the quality of code and improve security thinking.
Anyway, the logic for ignoring security is ok for a development box, given the scenario that it is pretty much inaccessible outside the development team. When going to production things always get more complicated. I have witnessed production boxes which are running applications that have been configured to access DB with Admin-permissions. That happens in an environment where any decent programmer/DBA can spot out a number of other ignored things. Thinking about security is both far above the pay-grade and the skill envelope your regular coder possesses.
In an attempt to do things the-right-way(tm), it is a really good idea to create a specific user for accessing the DB. Even better idea is to limit the permissions so, that application user cannot run the classic "; DROP TABLE users; -- " because lacking the permission to drop tables. We still remember Exploits of a Mom, right?
Image courtesy of xkcd.com.
Back to reality... I was on a production PostgreSQL and evaluated the situation. Database has owner of postgres, schema public had owner of postgres, but all the tables, sequences and views where owned by the application user. So any exploit would allow the application user to drop all tables. Not cool, huh!
To solve this three things are needed: first, owner of the entire schema must be postgres. Second, the application user needs only to have enough permission for CRUD-operations, nothing more. And third, the schema must not allow users to create new items on it. As default everybody can create new tables and sequences, but if somebody really pops your box and can run anything on your DB, creating new items (besides temporary tables) is not a good thing.
On a PostgreSQL 8 something of a trickery is needed. Version 9.0 introduced us the "GRANT ... ALL TABLES IN SCHEMA", but I didn't have that at my disposal. To get around the entire thing I created two SQL-queries which were crafted to output SQL-queries. I could simply copy/paste the output and run it in pgAdmin III query-window. Nice!
The first query to gather all tables and sequences and change the owner to postgres:
SELECT 'ALTER TABLE ' || table_schema || '.' || table_name ||' OWNER TO postgres;'
FROM information_schema.tables
WHERE
table_type = 'BASE TABLE' and
table_schema NOT IN ('pg_catalog', 'information_schema')
UNION
SELECT 'ALTER SEQUENCE ' || sequence_schema || '.' || sequence_name ||' OWNER TO postgres;'
FROM information_schema.sequences
WHERE
sequence_schema NOT IN ('pg_catalog', 'information_schema')
It will output something like this:
ALTER TABLE public.phones OWNER TO postgres;
ALTER SEQUENCE public.user_id_seq OWNER TO postgres;
I ran those, and owner was changed.
NOTE: that effectively locked the application user out of DB completely.
So it was time to restore access. This is the query to gather information about all tables, views, sequences and functions:
SELECT 'GRANT ALL ON ' || table_schema || '.' || table_name ||' TO my_group;'
FROM information_schema.tables
WHERE
table_type = 'BASE TABLE' and
table_schema NOT IN ('pg_catalog', 'information_schema')
UNION
SELECT 'GRANT ALL ON ' || table_schema || '.' || table_name ||' TO my_group;'
FROM information_schema.views
WHERE
table_schema NOT IN ('pg_catalog', 'information_schema')
UNION
SELECT 'GRANT ALL ON SEQUENCE ' || sequence_schema || '.' || sequence_name ||' TO my_group;'
FROM information_schema.sequences
WHERE
sequence_schema NOT IN ('pg_catalog', 'information_schema')
UNION
SELECT 'GRANT ALL ON FUNCTION ' || nspname || '.' || proname || '(' || pg_get_function_arguments(p.oid) || ') TO my_group;'
FROM pg_catalog.pg_proc p
INNER JOIN pg_catalog.pg_namespace n ON pronamespace = n.oid
WHERE
nspname = 'public'
It will output something like this:
GRANT ALL ON public.phones TO my_user;
GRANT ALL ON SEQUENCE public.user_id_seq TO my_user;
NOTE: you need to find/replace my_user to something that fits your needs.
Now the application was again running smoothly, but with reduced permission in effect. The problem with all this is that TRUNCATE-clause (or DELETE FROM -tablename-) are still working. To get the maximum out of enhanced security, some classification of data would be needed. But the client wasn't ready to do that (yet).
The third thing is to limit schema permissions so that only usage is allowed for the general public:
REVOKE ALL ON SCHEMA public FROM public;
GRANT USAGE ON SCHEMA public TO public;
Now only postgres can create new things there.
All there is to do at this point is to test the appliation. There should be errors for DB-access if something went wrong.