Alexandre Lemaire Alexandre Lemaire

Distributing a ZF2 Module through GitHub, Packagist & Composer

I was in an interesting situation where I wanted to build a custom ACL management panel for BjyAuthorize, and figured it was something I'd probably reuse time and again.  Seems to make sense as a vendor module in my ZF2 project, and it is something I would probably want to maintain independently of a particular project's scope.  If this thing is well built, it'll be standalone...amirite!

If you haven't yet used composer, it hauls a ton of ass (its website is a pretty dry read though...).  Part of my day's pleasure is running 'php composer.phar selfupdate' and 'php composer.phar update' while I take the first few sips of coffee to see what updates while I caffeinate.   Who else was working last night!

It's a pretty straight thing to configure; you'll find the tell-tale composer.json in the ZF2 Default Skeleton App if you started there (highly recommended).  My current big project's composer looks like:

{
"name": "zendframework/skeleton-application",
"description": "Skeleton Application for ZF2",
"license": "BSD-3-Clause",
"keywords": [
"framework",
"zf2"
],
"homepage": "http://framework.zend.com/",
"minimum-stability" : "dev",
"require": {
"php" : ">=5.3.3",
"zendframework/zendframework" : "2.1.5",
"doctrine/doctrine-orm-module" : "0.7.*",
"aws/aws-sdk-php-zf2" : "dev-master",
"zf-commons/zfc-twig" : "dev-master",
"zf-commons/zfc-base" : "0.1.*",
"zf-commons/zfc-user" : "0.1.*",
"zf-commons/zfc-user-doctrine-orm" : "0.1.*",
"bjyoungblood/BjyAuthorize" : "dev-master",
"zendframework/zend-developer-tools": "dev-master",
"saeven/circlical-acl-admin": "*"
}
}

That last line down there, the saeven/circlical-acl-admin -- is the package we want to create (these blog articles are strange that way aren't they, we write them as a recap of what we've done, but in future tense for readability).

 

First Step - GitHub

You can create a repo to distribute your source to Packagist in different ways (Git/Svn/Hg); subscribing to Occam's Razon, GitHub is a pretty convenient means to get this done.  If you're working under the scrutiny of some IS nerds with badges, or are working on top-secret stuff, you'll use a different source repo for Packagist (you probably won't even use Packagist, you'll use Satis - blog post for another day). 

Sign up for a free account at GitHub if you don't have one, and create a repository (it's that book-looking icon at top right). 

 

Create a new repo button on GitHub

Create a new repo button on GitHub

Be a good sport, and name the repo after the ZF2 module name it'll be housing, give it a quick description, and optional, initialize it with a ZendFramework .gitignore.

 

Once this is done, leave the tab open -- we'll need it later, but, you are theoretically done here.  The confirmation page at GitHub gives you the command line syntax to commit and push code; but you are using a GitHub ready IDE -- right?

 

Second Step - Module Layout & Config

Here's where we configure the Module you're offering the world, and the composer config nested within it.  For this article's sake, we'll give it a basic route, and rig things to be PSR-0 compliant. 

Fire up your IDE, create a new project with this folder structure. 

Project layout (in PhpStorm w/Darcula theme)

Project layout (in PhpStorm w/Darcula theme)

If you're not familiar with ZF2 module layouts, it does matter.  You'll notice, contrary to the skeleton app (at least at present day), there's a Module.php in the base folder and  in the src/ModuleName/ folder.  This is that PSR-0 compliant effort we were talking about.  

The code in the base-folder Module.php looks like so:

/Module.php

<?php
/**
* This file is placed here for compatibility with ZendFramework 2's ModuleManager.
* It allows usage of this module even without composer.
*/
require_once __DIR__ . '/src/CirclicalACLAdmin/Module.php';

We notice that it defers to the Module.php tucked in the src folder, which naked, like so:

src/CirclicalACLAdmin/Module.php

<?php
/**
* Circlical ACL Admin Module for BjyAuthorize Module
*
* @link TBD for the Circlical ACL Admin canonical source repository
* @link https://github.com/bjyoungblood/BjyAuthorize for the BjyAuthorize canonical source repository
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace CirclicalACLAdmin;

use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\ControllerPluginProviderInterface;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
use Zend\Mvc\ApplicationInterface;

/**
* Circlical ACL Admin Interface for BJYAuthorize
*
* @author Alexandre Lemaire <alemaire@circlical.com>
*/
class Module implements
AutoloaderProviderInterface,
BootstrapListenerInterface,
ConfigProviderInterface,
ControllerPluginProviderInterface,
ViewHelperProviderInterface
{
/**
* {@inheritDoc}
*/
public function onBootstrap(EventInterface $event)
{
/* @var $app \Zend\Mvc\ApplicationInterface */
$app = $event->getTarget();
}

/**
* {@inheritDoc}
*/
public function getViewHelperConfig()
{
return array(
'factories' => array(

),
);
}

/**
* {@inheritDoc}
*/
public function getControllerPluginConfig()
{
return array(
'factories' => array(

),
);
}

/**
* {@inheritDoc}
*/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/../../src/' . __NAMESPACE__,
),
),
);
}

/**
* {@inheritDoc}
*/
public function getConfig()
{
return include __DIR__ . '/../../config/module.config.php';
}
}

Bare-bones, it should be enough to get you started with most of the common connection points.  The standard getConfig() is there, referencing config/module.config.php.  We're going to pimp ours out a bit though, I have a two fundamental requirements of my module:

  1. Capture a basic route of: acl-admin to show an index page
  2. Be guarded by BjyAuthorize itself

Reverse engineering a bit, I'll put the following in my config and reverse engineer the details into my Module.php.

config/module.config.php

<?php


return array(


'bjyauthorize' => array(

// Guard listeners to be attached to the application event manager
'guards' => array(
'BjyAuthorize\Guard\Controller' => array(
array('controller' => 'CirclicalACLAdmin\Controller\Index', 'roles' => array( 'user' ) ),
),
),

),


'controllers' => array(
'invokables' => array(
'CirclicalACLAdmin\Controller\Index' => 'CirclicalACLAdmin\Controller\IndexController'
),
),


'service_manager' => array(

'factories' => array(

),

'initializers' => array(

),
),


'view_manager' => array(
'template_map' => array(
'circlical-acl-admin/index' => __DIR__ . '/../view/circlical-acl-admin/index.phtml',
),
),


'router' => array(

'routes' => array(

'circlical-acl-admin' => array(

'type' => 'Literal',
'priority' => 1000,
'options' => array(
'route' => '/acl-admin',
'defaults' => array(
'controller' => 'CirclicalACLAdmin',
'action' => 'index',
),
),
'may_terminate' => true,

),
),
),
);

I added a bare-bones view and controller in there, you can get them in the full source archive at the end (plain old ZF2, no rocket science there).  Roll your own, or skip down and cheat (and the come back here) and let's look at the composer specification that we need to create in the Module's base folder, composer.json (don't confuse with the composer.json that is put in your project base to pull files.  This one is more of a push manifest).

{
"name": "saeven/circlical-acl-admin",
"description": "Admin moderation panel for bjyoungblood/bjy-authorize",
"type": "library",
"license": "BSD-3-Clause",
"homepage": "https://github.com/Saeven/CirclicalACLAdmin",
"keywords": [
"zf2",
"acl",
"zfc-user",
"bjyauthorize",
"circlical"
],
"authors": [
{
"name": "Alexandre Lemaire",
"email": "alemaire@circlical.com",
"homepage": "http://circlical.com/",
"role": "Developer"
}
],
"minimum-stability": "dev",
"require": {
"php": ">=5.3.3",
"bjyoungblood/BjyAuthorize": "~1.3"
},
"require-dev": {
"phpunit/phpunit": "~3.7",
"doctrine/common": ">=2.3,<2.5-dev"
},
"suggests": {
"zf-commons/zfc-user-doctrine-orm": "If you want to use BjyAuthorize with Doctrine ORM"
},
"autoload": {
"psr-0": {
"CirclicalACLAdmin\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.1-dev"
}
}
}

Super straight forward, I'll spare your eyeballs the play-by-play; but there are some important details we'll carry into the subsequent steps: 

  1. module name, saeven/circlical-acl-admin.  This is what'll appear in Packagist.
  2. homepage, so that people can read about your work when they discover it through Packagist
  3. keywords, these appear as tag-bubbles in the Packagist display
  4. authors, be proud
  5. autoload.psr-0 - put your Module name here, note the escaped backslash \\ 
  6. require - put the packagist packages that your package requires here

Save that in the base. 

Here's the full source. 

If you're using PhpStorm, pushing to GitHub is really simple.   You can otherwise do it from the command line too -- get your code into GitHub.

 

Third Step - Code Ready, Push to Packagist

Your code in GitHub, head to https://packagist.org/, log in, and head to the Submit Package area. Punch in your GitHub repo URL, click "Check" and follow the steps.  

Screen Shot 2013-06-24 at 9.44.04 PM.png

Do connect your Packagist repo to GitHub, so that pushes to your repo make their way to packagist as well.  Steps are on-screen when you complete your Packagist submission.

Grand Finale

If you completed all the steps, you should be able to simply add your Packagist package name to your application project's (not the one we rigged here, another) composer.json and run 'php composer.phar update' to see your source download and rig itself with all the right autoloaders.  It'll even trap whatever route you configured if you did like me above.  Now how cool is that!

Or, save now, and do it tomorrow over coffee ;)

If you want me to clarify any of the steps above, drop a comment and it's my pleasure to expand on any specific section!

Read More
Alexandre Lemaire Alexandre Lemaire

From ZF1 Zend_Db Queries to ZF2 \Zend\Db\Sql\Sql?

If you're converting over to ZF1 from ZF2, there's quite a few subtleties to bridge.  I hope this helps you bridge the gap between the two.  By no means thorough, I just wanted to highlight the close-but-not-quite elements that would helped me get kickstarted.

Also, if you're looking to connect two databases to ZF2, this will probably help. 

If you're converting over to ZF1 from ZF2, there's quite a few subtleties to bridge.  Most of the docs are pretty clear, but I found some aspects of Zend_Db's successor to be a bit more obscure.  Zend_Db was a good friend, especially when came time to adopt query abstraction without going through table gateways.  It was so easy (probably sounding little spoiled now) to do then what I wanted to do now, which is wire a connection outside of Doctrine, in order to connect to an AWS Redshift Cluster (which is awesome by the way) to execute straight queries.

What I Used To Do, That I Wanted Anew

$DB = Zend_Db::factory
(
'Pdo_Mysql',
array(
'host' => 'host',
'password' => $this->getDatabasePass(),
'username' => $this->getDatabaseUser(),
'dbname' => $this->getDatabaseName(),
'options' => array(
Zend_Db::AUTO_QUOTE_IDENTIFIERS
)
)
);

$S = $DB->select()->from( 'table', array( 'col1', 'col3' ) )
->where( 'col2=?', "O'Smith" )
->limit( 1 );
$R = $DB->fetchAll( $S );

You'll see below that things have taken a bit of a turn for the more complex. Reading the rationale in all the PRs on GitHub is a bit of a brain-bender, and when you use something like Doctrine for most of the day to day, this is probably a fringe-case thing.

 

Prepping The New Mistress (ZF2)

I first recommend that you wire your connection the ZF2 way.  I've got Doctrine2 already setup in my autoload configuration file -- just need to add a few lines for configuration's sake.

config/autoload/database.local.php

<?php

return array(
'doctrine' => array(
'connection' => array(
'orm_default' => array(
'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
'params' => array(
'host' => 'host',
'port' => '3306',
'user' => 'user',
'password' => 'pass',
'dbname' => 'doctrine_mysql_db',
)
)
)
),
'redshift' => array(
'connection' => array(
'stats_default' => array(
'driver' => 'Pdo_Pgsql',
'host' => 'host',
'dbname' => 'analytics',
'username' => 'redshift_user',
'password' => 'redshift_pass',
'port' => 5439,
'charset' => 'UTF8'
),
),
),
);
You'll notice that I'm using Pgsql above, you may want to change if you're going to use MySQL for example - just need to change the driver string

This done, edit your Module config at the getServiceConfig() level to include a factory to dispatch the connection:

module/Application/Module.php

public function getServiceConfig() 
{
return array(
'factories' => array(
// it's this part that matters
'redshift_stats' => function ($sm) {
$config = $sm->get('config');

$dbc = $config['redshift']['connection']['stats_default'];
$adapter = new \Zend\Db\Adapter\Adapter( $dbc );
return new \Zend\Db\Sql\Sql( $adapter );
},
// end, part that matters
),
);
}

(your mileage may vary) 

Thusly is born the part that lets you do what's below, which is the equivalent to the first (I thought succinct!) ZF1 block we started with: 

$SQL  = $this->getServiceLocator()->get('redshift_stats');
$DBA = $SQL->getAdapter();
$S = $SQL->select()
->from( 'table' )
->columns( array( 'col1', 'col3' ) )
->where( array( "col2" => "O'Smith" ) )
->limit( 1 );

$sel_string = $SQL->getSqlStringForSqlObject($S);
$R = $DBA->query( $sel_string, $DBA::QUERY_MODE_EXECUTE );

Oh yea, fetchAll, fetchOne, and fetchPairs are all gone...  (I ran across a bridge on GitHub someone wrote to try to bridge this extricated functionality).

 

Insertion Queries

Inserts and updates work a bit differently with ZF2 as well.  The ZF1 insert and update would execute right away, you have to extract the string and execute it now.  This can be a gotcha as you get started

With Zend Framework 1

$DB = /* get your connection */;
$DB->insert( 'table', array( 'col1' => 123 ) );

With Zend Framework 2

$DB       = $this->getServiceLocator()->get('redshift_stats'); 
$ADAPTER = $SQL->getAdapter();
$I = $SQL->insert( 'table' );

$I->values( array( 'col1' => 123 ) );

$ins_string = $SQL->getSqlStringForSqlObject($I);
$results = $ADAPTER->query( $ins_string, $ADAPTER::QUERY_MODE_EXECUTE );

Concluding, I hope this helps you bridge the gap between the two.  By no means thorough, I just wanted to highlight the close-but-not-quite elements that would helped me get kickstarted.

The verbosity of it all, and Zend\Db's lack of support for batch inserts (yes I prefer MySQL) really makes adopting the likes of Doctrine2 worthwhile (I find the Mappers turn out cleaner overall).  I think this may have been the MO with ZF2, less ORM, more query abstraction -- please use something else like Doctrine2 for heavy -- scratch that -- usual lifting.

 

Read More
Alexandre Lemaire Alexandre Lemaire

Adding Facebook Applications (Canvas) to Location Pages

Developed a Facebook application whose canvas you want to add as a "Tab" to a Location?  Here's the secret recipe.

After a bit of sweat, here's the solution -- I hope this saves another soul a few moments of their life.  In the end, it's a matter of getting and reusing a few variables.  Prerequisites I won't cover are that you have a Facebook app created and ready to go, and a Location page to which you want to add the app canvas.  The variables we'll touch are :

  1.  Facebook App ID
  2.  Facebook App Access Token w/manage_pages permission
  3.  Parent Page ID
  4.  Parent Page Access Token
  5.  Location Page ID
  6.  Location Page Access Token

Getting the Deed Done

  • First, go to the graph explorer at http://developers.facebook.com/tools/explorer/
  • Select your application in the drop-down at top right
  • Click on Get Access Token
  • In the dialog that appears, go to Extended Permissions, and ensure that "manage_pages" is checked
  • Click Get Access Token in the Dialog
  • The token will auto-populate in the Access Token box within the Graph Explorer
  • Change the Graph API textfield to:  /me/accounts and request via GET
  • The JSON list will return the pages you have rights to administer.  If the parent page (parent to the location you want to add a canvas to) is not listed, you don't have the necessary rights -- get them first, then continue. ote the access token and id of the parent page.
  • Copy and paste the parent page access token into the Access Token textfield at the top of the explorer
  • Copy and paste the parent page id into the request box, changing it to PARENT_PAGE_ID/locations
  • Submit via GET.  That'll list all locations.  You should see your location listed.  If you have a ton of them and the list is a pain to read, you can lift it from the URL while you're inside your Location's admin settings panel.  Copy your location id
  • hange the request box to:  LOCATION_ID?fields=access_token
  • Submit via GET
  • The response will contain the location id (repeated) and the location access token  
  • Copy and paste the location access token into the Access Token textfield
  • Change the graph URL to LOCATION_ID/tabs and the method to POST
  • dd a variable `app_id` whose value is the app id whose canvas you want to add (you get the app id from the developer apps panel)
  • Submit (via POST) -- the response will be 'true'

Worked for me, repeated twice, what a PITA.  Good luck.

Read More
Alexandre Lemaire Alexandre Lemaire

ZF2, Doctrine2, ZfcUser - customizing the registration form and storing the custom user entity

ZFCUser is a great module, really cleanly written, and while there are some modules out there that'll help you extend ZFCUser with custom profiles, my challenge was one where I had to retrofit an existing Doctrine entity for use with ZFCUser -- let me tell you it worked great.

There are a lot of posts on SO that have to do with granular pieces of extending the ZfcUser form, but nothing I could find that had a usable A to Z for Doctrine2 users.  I added a post on the ZFCUser Wiki about the storage piece, and thought I'd throw it in here to help the next wandering coder.

Assuming that you've done two things:

  •  set up your application.config.php to include ZfcBase, ZfcUser, ZfcUserDoctrineORM
  • created your own user Module (e.g., module/FooUser/) with your entities and schema already set up

Essentially, you have a working ZfcUser, but just need to know how to extend the form and store (quite a few reasonable tutorials out there if you're not here yet.  Find one, set it up, and then come back).

/module/FooUser/config/module.config.php

<?php

return array(
'doctrine' => array(
'driver' => array(
// overriding zfc-user-doctrine-orm's config
'zfcuser_entity' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'paths' => __DIR__ . '/../src/FooUser/Entity',
),

'orm_default' => array(
'drivers' => array(
'FooUser\Entity' => 'zfcuser_entity',
),
),
),
),

'zfcuser' => array(
// telling ZfcUser to use our own class
'user_entity_class' => 'FooUser\Entity\User',
// telling ZfcUserDoctrineORM to skip the entities it defines
'enable_default_entities' => false,
),
);

This tells your module config to use the entities you've set up in /modules/FooUser/src/FooUser/Entity/User.php for example, as the default ZfcUser User entity.

/module/FooUser/Module.php

<?php

namespace FooUser;

use Zend\Mvc\MvcEvent;

class Module {

public function getConfig()
{
return include __DIR__ . '/config/module.config.php';

}

public function getAutoloaderConfig()
{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}

public function onBootstrap( MVCEvent $e )
{
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
$em->attach(
'ZfcUser\Form\RegisterFilter',
'init',
function( $e )
{
$filter = $e->getTarget();
// do your form filtering here
}
);

// custom form fields

$em->attach(
'ZfcUser\Form\Register',
'init',
function($e)
{
/* @var $form \ZfcUser\Form\Register */
$form = $e->getTarget();
$form->add(
array(
'name' => 'username',
'options' => array(
'label' => 'Username',
),
'attributes' => array(
'type' => 'text',
),
)
);

$form->add(
array(
'name' => 'favorite_ice_cream',
'options' => array(
'label' => 'Favorite Ice Cream',
),
'attributes' => array(
'type' => 'text',
),
)
);
}
);

// here's the storage bit

$zfcServiceEvents = $e->getApplication()->getServiceManager()->get('zfcuser_user_service')->getEventManager();

$zfcServiceEvents->attach('register', function($e) {
$form = $e->getParam('form');
$user = $e->getParam('user');
/* @var $user \FooUser\Entity\User */
$user->setUsername( $form->get('username')->getValue() );
$user->setFavoriteIceCream( $form->get('favorite_ice_cream')->getValue() );
});

// you can even do stuff after it stores
$zfcServiceEvents->attach('register.post', function($e) {
/*$user = $e->getParam('user');*/
});
}
}

Hopefully this helps another soul out!  This is one way to slice the pie that woulda saved me a bunch of time.

Tack BjyAuthorize on top of this, and you're off to the races.

https://packagist.org/packages/bjyoungblood/BjyAuthorize

Good Luck!

Read More
Alexandre Lemaire Alexandre Lemaire

Good trick to trash missing SVN files

Sometimes you need to clean up files in a repo against a DB (image assets come to mind).   Can't commit missing files in your IDE after you do the deed though.  Run this from within the working copy that contains "Missing" assets:

svn status | grep '\!' | awk '{print $2;}' | xargs svn rm
Read More