Targeting MongoDB driver version with Puppet

As mentioned here for our current project we are using Vagrant and Puppet.
In the link above I described how I solved the pear packages installation under Puppet.

We are using Symfony 2.0.15 with MongoDB, I need to use version 1.2.9 to avoid the whole setSlaveOK hubbub as described here.

The original puppet task we used looked like:

1
2
3
4
5
# install mongodb driver
exec { "/usr/bin/pecl install -f mongo-1.2.9":
    unless  => "/usr/bin/pecl info mongo",
    require => Exec["pear update-channels"]
}

Which would be great apart from one little detail, Puppet Pear setup executes the

1
pear upgrade

command which updates all the pear packages and updates the MongoDB driver to the latest version (1.3.9 at the time of writing), which then breaks the project and

1
sudo pecl install -f mongo-1.2.9

would have to be executed manually to bring order to chaos.

Luckily with a bit of shell scripting we can solve that by executing

1
test `pecl info mongo | grep 'Release Version' | tr -s '[:blank:]' | cut -d ' ' -f3` != '1.2.9'

which will return 0 if the condition is true (namely Release Version number is not 1.2.9).

By adding the one-liner above to the onlyif parameter of the exec task we can ensure that if the MongoDB driver version is not 1.2.9 the task will be executed.

The task now looks like this, note the use of full path:

1
2
3
4
5
# install mongodb driver
exec { "/usr/bin/pecl install -f mongo-1.2.9":
    require => Exec["pear update-channels"],
    onlyif => "/usr/bin/test `pecl info mongo | grep 'Release Version' | tr -s '[:blank:]' | cut -d ' ' -f3` != '1.2.9'"
}

Tags: , , ,

Pear Packages installation under Vagrant with Puppet

I have been running Vagrant vm with Puppet for the last 4 months for my development work.
Most of the vagrant stuff is pretty straight forward, once you grasp the concepts.
This post will assume that you are familiar with Vagrant and Puppet, if you are not this is a great place to start.

One aspect of my setup was lacking pear packages installation was prone to errors and as a result of that failed dependencies.
More often than not I would get this fun little error message

1
err: /Stage[main]//Exec[pear upgrade]/returns: change from notrun to 0 failed: /usr/bin/pear upgrade returned  instead of one of [0] at /tmp/vagrant-puppet/manifests/base.pp:98

All other puppet tasks that rely on this one would be skipped due to failed dependencies.

The exec command looks like this:

1
2
3
4
5
# upgrade pear
exec {"pear upgrade":
  command => "/usr/bin/pear upgrade",
  require => Package['php-pear'],
}

The problem arises from the fact that pear upgrade will return a null or ” value if there is nothing to update, and puppet will misinterpret that as an error as you can see in the error message above.

One solutions that I found was to add the possible returned values like this:

1
2
3
4
5
6
# upgrade pear
exec {"pear upgrade":
  command => "/usr/bin/pear upgrade",
  require => Package['php-pear'],
  returns => [ 0, '', ' ']
}

On line 5 the returned values are defined, 0 is the default one, and I added the empty string and space just in case, that resolved the problem of the pear upgrade.

To keep things simple, pear should be allowed to auto-discover channels and dependencies.

1
2
3
4
5
# set channels to auto discover
exec { "pear auto_discover" :
  command => "/usr/bin/pear config-set auto_discover 1",
  require => [Package['php-pear']]
}

And to wrap up the prerequisites for pear setup the pear update-channels should be executed.
It may not be necessary, but in some cases it was reported that this command would prevent the pear errors during installation of pear packages.

1
2
3
4
exec { "pear update-channels" :
  command => "/usr/bin/pear update-channels",
  require => [Package['php-pear']]
}

So we come to the last piece of the puzzle, if you try to reinstall the pear package, you will get the message that the package is already installed and the task will fail.

The solution for this was a tip from Robert Basic.

I need to use the creates parameter, that checks if the file exists, and if it does not, the install will be attempted.
So here is how I installed a good portion of tools from The PHP Quality Assurance Toolchain

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
exec {"pear install phpunit":
  command => "/usr/bin/pear install --alldeps pear.phpunit.de/PHPUnit",
  creates => '/usr/bin/phpunit',
  require => Exec['pear update-channels']
}

# install phploc
exec {"pear install phploc":
  command => "/usr/bin/pear install --alldeps pear.phpunit.de/phploc",
  creates => '/usr/bin/phploc',
  require => Exec['pear update-channels']
}

# install phpcpd
exec {"pear install phpcpd":
  command => "/usr/bin/pear install --alldeps pear.phpunit.de/phpcpd",
  creates => '/usr/bin/phpcpd',
  require => Exec['pear update-channels']
}

# install phpdcd
exec {"pear install phpdcd":
  command => "/usr/bin/pear install --alldeps pear.phpunit.de/phpdcd-beta",
  creates => '/usr/bin/phpdcd',
  require => Exec['pear update-channels']
}

# install phpcs
exec {"pear install phpcs":
  command => "/usr/bin/pear install --alldeps PHP_CodeSniffer",
  creates => '/usr/bin/phpcs',
  require => Exec['pear update-channels']
}

# install phpdepend
exec {"pear install pdepend":
  command => "/usr/bin/pear install --alldeps pear.pdepend.org/PHP_Depend-beta",
  creates => '/usr/bin/pdepend',
  require => Exec['pear update-channels']
}

# install phpmd
exec {"pear install phpmd":
  command => "/usr/bin/pear install --alldeps pear.phpmd.org/PHP_PMD",
  creates => '/usr/bin/phpmd',
  require => Exec['pear update-channels']
}

# install PHP_CodeBrowser
exec {"pear install PHP_CodeBrowser":
  command => "/usr/bin/pear install --alldeps pear.phpqatools.org/PHP_CodeBrowser",
  creates => '/usr/bin/phpcb',
  require => Exec['pear update-channels']
}

Tags: , , ,

Automated database changes management with Ant and dbdeploy

In the last couple of months we started using Ant tool heavily to do server deployments, along with Jenkins server (although not in the role it was intended, but hey, it serves the purpose).

Most of the things in setup work well and it became a pretty smooth operation, but one big hurdle that remained was the db changes management.

Lack of time prevented me from exploring the options, but as luck would have it, we were attending the awesome phpDay 2012 conference, and an exellent talk by Jeremy Coates titled An introduction to Phing the PHP build system where on one of the slides two tools were mentioned, dbdeploy and Liquidbase.

I did not hear about them up till then, and they were mentioned briefly as db change automation tools, and those words rang in my ears, I could not believe my luck, so few days after, I started looking into them.

What I wanted to have is a way to incrementaly update the database, or just dump all the alters in, a nice side effect would be to be able to generate one flat deploy file that could be either user for some remote deployment, or submitted to the DBA if needed.

Both tools look excellent, and they seem to do their job well, but in the end I selected the dbdeploy because it was closer to the way we structure our projects and files, and would be less intrusive to our process.

Dbdeploy relies on deltas to update the db, each delta is one file that should ideally have one CREATE/ALTER/DROP command and it’s undo counterpart, the files should be named in form XXX_something_or_other.sql, where XXX is numeric that identifies the order of execution of the script, with that in mind, it goes along nicely with the way we work (granted naming of directories is different, but it works, for now…).
We would have to adapt a bit to break down the initial db draft into individual alter files, and add their undos, but that is really a non-issue.

The setup consists of

  1. Ant
  2. dbdeploy, which you can get from here
  3. MySql Connector JDBC driver, which you can get from here, or some other JDBC driver (I am targeting MySql so YMMV)

The tasks/targets are defined in the build.xml file, more on that later, for the moment I’d like to focus on the build.properties file.
This file is not supposed to go into the VCS apart in it’s build.properties.dist form (which you should rename and mod for your local setup).
This file will contain some paths and db credentials that vary from project to project and/or server to server.
This file is a standard ant properties file which consits of key/value pairs

build.properties:

1
2
3
4
5
6
7
8
9
10
11
12
# Property files contain key/value pairs
#key=value

# Credentials for the database migrations
db.host=localhost
db.port=3306
db.user=root
db.pass=root
db.name=dbdeploy

# Path to mysql
progs.mysql=/usr/bin/mysql

This is pretty straight forward stuff, but point of interest is line 12 – path to mysql, you may want to drop this, I used it to execute the flat sql file with mysql, more like a proof that it works than actual use

With that the variable properties are covered and the build.xml file can be built. My personal preference is that it goes in the VCS, but some prefer to keep only the build.xml.dist in the VCS.

build.xml:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?xml version="1.0" encoding="UTF-8"?>

<project name="dbDeploy_example" basedir="." default="default">
    <description>This is an ant build.xml file for the example dbdeploy project.</description>

    <!-- Define the timestamp format for the generated files -->
    <tstamp>
        <format property="current.time" pattern="yyyy-MM-dd-HH-mm-ss"/>
    </tstamp>

    <!-- Load our configuration -->
    <property file="./build.properties"/>

    <!-- Define the sources dir -->
    <property name="src" value="."/>

    <!-- Define the path to the dbdeploy dir -->
    <property name="build.dbdeploy.dbdeploy_dir" value="${src}/../dbdeploy"/>

    <!-- Define the path to the deltas/alters dir -->
    <property name="build.dbdeploy.alters_dir" value="${src}/../db/alters"/>

    <!-- Define the path to the deploy flat file dir -->
    <property name="build.dbdeploy.deploy_dir" value="${src}/../db/deploy"/>

    <!-- Define the path to the undo flat file dir -->
    <property name="build.dbdeploy.undo_dir" value="${src}/../db/undo"/>

    <!-- Last change number to apply, useful for preventing the unchecked delta to mess things up -->
    <property name="build.dbdeploy.lastChangeToApply" value="20"/>

    <!-- Define the db driver -->
    <property name="db.driver" value="com.mysql.jdbc.Driver"/>

    <!-- Define the url to the database -->
    <property name="db.url" value="jdbc:mysql://${db.host}:${db.port}/${db.name}"/>

    <!-- Define the target DBMS -->
    <property name="db.dbms" value="mysql"/>

    <!-- Define tha path to the changelog table sql file -->
    <property name="build.dbdeploy.changelog_file" value="${build.dbdeploy.deploy_dir}/scripts/createSchemaVersionTable.mysql.sql"/>

    <!-- these two filenames will contain the generated SQL to do the deploy and roll it back-->
    <property name="build.dbdeploy.deployfile" value="deploy-${current.time}.sql"/>
    <property name="build.dbdeploy.undofile" value="undo-${current.time}.sql"/>

    <property name="use-verbose" value="false"/>

    <!-- Define the classpath for the db driver -->
    <path id="mysql.classpath">
        <fileset dir="${build.dbdeploy.dbdeploy_dir}">
            <include name="mysql*.jar"/>
        </fileset>
    </path>

    <!-- Define the classpath for the dbdeploy -->
    <path id="dbdeploy.classpath">
        <!-- include the dbdeploy-ant jar -->
        <fileset dir="${build.dbdeploy.dbdeploy_dir}">
            <include name="dbdeploy-ant-*.jar"/>
        </fileset>

        <!-- The dbdeploy task also needs the database driver jar on the classpath -->
        <path refid="mysql.classpath"/>
    </path>

    <!-- Declare the dbdeploy task -->
    <taskdef name="dbdeploy" classname="com.dbdeploy.AntTarget" classpathref="dbdeploy.classpath"/>

    <!-- Target to generate the changelog table in the database -->
    <!-- This should be run only the first time db is created (and if it is ever recreated), so ugly, but works -->
    <target name="create-changelog-table">
        <sql driver="${db.driver}" url="${db.url}"
            userid="${db.user}" password="${db.pass}" classpathref="mysql.classpath">
            <fileset file="${build.dbdeploy.changelog_file}"/>
        </sql>
    </target>

    <!-- Target to generate two scripts, one for deploy, the other for rollback to the version specified in the build properties file,
        useful when you want to submit to DBA for review -->
    <target name="dbdeploy-generate-sql">

        <!-- Generate the directories for the deploy and undo files -->
        <mkdir dir="${build.dbdeploy.deploy_dir}" />
        <mkdir dir="${build.dbdeploy.undo_dir}" />

        <!-- generate the deployment scripts -->
        <dbdeploy
               driver="${db.driver}"
               url="${db.url}"
               userid="${db.user}"
               password="${db.pass}"
               dir="${build.dbdeploy.alters_dir}"
               outputfile="${build.dbdeploy.deploy_dir}/${build.dbdeploy.deployfile}"
               undooutputfile="${build.dbdeploy.undo_dir}/${build.dbdeploy.undofile}"
               dbms="${db.dbms}"
               lastChangeToApply="${build.dbdeploy.lastChangeToApply}"
               />
    </target>

    <!-- Target to generate two scripts, one for deploy, the other for rollback, useful when you want to submit to DBA for review -->
    <target name="dbdeploy-generate-sql-all">

        <!-- Generate the directories for the deploy and undo files -->
        <mkdir dir="${build.dbdeploy.deploy_dir}" />
        <mkdir dir="${build.dbdeploy.undo_dir}" />

        <!-- generate the deployment scripts -->
        <dbdeploy
               driver="${db.driver}"
               url="${db.url}"
               userid="${db.user}"
               password="${db.pass}"
               dir="${build.dbdeploy.alters_dir}"
               outputfile="${build.dbdeploy.deploy_dir}/${build.dbdeploy.deployfile}"
               undooutputfile="${build.dbdeploy.undo_dir}/${build.dbdeploy.undofile}"
               dbms="${db.dbms}"
               />
    </target>

    <!-- Target to actually do the migration to the version specified in the build properties file -->
    <target name="dbdeploy-migrate">

        <!-- generate the deployment scripts -->
        <dbdeploy
               driver="${db.driver}"
               url="${db.url}"
               userid="${db.user}"
               password="${db.pass}"
               dir="${build.dbdeploy.alters_dir}"
               dbms="${db.dbms}"
               lastChangeToApply="${build.dbdeploy.lastChangeToApply}"
               />

    </target>


    <!-- Target to actually do the migration to the latest version -->
    <target name="dbdeploy-migrate-all">

        <!-- generate the deployment scripts -->
        <dbdeploy
               driver="${db.driver}"
               url="${db.url}"
               userid="${db.user}"
               password="${db.pass}"
               dir="${build.dbdeploy.alters_dir}"
               dbms="${db.dbms}"
               />

    </target>

    <!-- Target to import the geenrated deploy sql file into db via mysql -->
    <target name="dbdeploy-execute-sql" depends="dbdeploy-generate-sql">
        <!-- execute the SQL - Use mysql command line to avoid trouble with large files or many statements and PDO -->
        <exec
               command="${progs.mysql} -h${db.host} -u${db.user} -p${db.pass} ${db.name} &lt; ${build.dbdeploy.deployfile}"
               dir="${build.dir}"
               checkreturn="true"/>
    </target>
</project>

Points of interest:

  • Line 18 – the path to the dbdeploy and MySql JDBC connector jars, because this will be standardized structure it makes more sense to set it here, than in build.properties
  • Lines 20 – 27 – the paths for the deltas, deploy and undo scripts, they are defined relatively to the build.xml file or basedir definition
  • Line 30 – build.dbdeploy.lastChangeToApply key – this is basically a limiter, if you have new deltas you are working on, and do not want them in the db, you can prevent their execution by setting the number to lower than the order number they have.
  • Line 33 – the package for your db driver, to decrease the typos possible when generating a new target
  • Line 36 – the url of the database
  • Line 42 – the path to the changelog table script, it comes with dbdeploy package, and is IMPORTANT, dbdeploy uses it to track changes
  • Lines 45 – 46 – definiton for the deploy and undo files, I chose to use timestamp value after the name
  • Lines 51 – 55 – define classpath for the MySql Connector and the location of the jars for it
  • Lines 58 – 66 – define classpath for the dbdeploy and the location of the jars for it
  • Line 69 – declared the dbdeploy task to Ant
  • Lines 73 – 78 – This is the target that actually creates the changelog table in your db, should be used once or when recreating the db. Without this table the dbdeploy will fail
  • Lines 155 – 161 – This is more a proof of concept than it actually works kinda thing, it can be used do import the generated deploy file into the db via shell command

You might notice that the actual targets to execute are doubled, their syntax is almost the same, they differ in one important aspect.
The *-all targets will execute ALL the deltas present indiscriminantly.

The targets without the -all will rely on the build.dbdeploy.lastChangeToApply from build.properties file to execute the deltas only upto, and including the number given.

This is important to us, because this way we can make sure that only the deltas that are verified as good be executed in the db, or to shoot ourselves in the foot and lose a couple of hours trying to figure out why delta with order number x is not executed while not realising that the build.dbdeploy.lastChangeToApply value y is less than x, SO BEWARE OF THE LIMITER

The targets dbdeploy-generate-sql/dbdeploy-generate-sql-all will generate two files (flat sql files), one for deploy on location set by build.dbdeploy.deploy_dir, and one for undo on location set by build.dbdeploy.undo_dir. These files can either be executed on remote server or submitted to your DBA for review, or some other purpose you may need.

The targets dbdeploy-migrate/dbdeploy-migrate-all will update your db schema with the deltas that were not yet executed.

You will notice that I do not mention the undo/rollback process, from what I understand the dbdeploy currently does not support rolling back to a specific script, and it’s undo will happen only if the update is broken.
Dbdeploy will assume you will fix the problem by making another delta and executing it on the db.

For our scenarios that is currently a non-issue because we test things locally and on dev server before we deploy to live. Chances of immediate rollback are small, yet still present.

The files are available on Github

Tags: , , ,

Getting MAMP to play nice with vhosts



Front end developers in the team we are a part of are using MAMP to serve their websites localy.
Aside from that they had virtually no experience setting up vhosts on it.
So I thought I should put this little tutorial to help them out a bit.


First things first is to get and install MAMP, you can get it from http://www.mamp.info/en/index.html


The free version will suffice, download it, unpack it, run the pkg instaler.
Once the process is finsihed, open the MAMP dir in applications in your finder.
Go into the conf/apache directory, you should see couple of files, but one called httpd.conf is the one that interests us.


Open the httpd.conf with TextEdit (or your favourite editor), and scroll to the end of the file.
Near the end you should find a section that looks like following:

1
2
# Virtual hosts
Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf

What this line says is that the vhosts definitions are going to be in one file called httpd-vhosts.conf and that one is located deep into the Applications dir.


I prefer to keep my vhosts files in my home directory, inside of directory imaginatively called Sites.
At this point I will assume that you would like something like that as well.
So, lets tell MAMP to look for our vhosts files in the Sites directory.
We do that by adding

1
Include /Users/user/Sites/*.conf

under the current include line.


It should look something like

1
2
3
# Virtual hosts
Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf
Include /Users/user/Sites/*.conf

Do remember to change the user in the path to your username.


Now create the Sites directory in your home directory and (re)start MAMP and you are done.
For seting up the vhosts file check out my post on the subject.


So from now on, if you want to enable virtual host site, you need to make a sitename.conf file and restart MAMP.
And if you want to disable some site, just add .disabled (or some other extension) to the end of file name and restart MAMP.

Tags: , , , ,

Batch file rename from shell

Recently I needed to reuse some files by renaming them or just parts of the name that have a certain word in it.
I started doing it manually, but as you can imagine it is a tedious, lengthy and error prone process.
There had to be a better way to do it.
As I have learned in recent months, there usually is and is usually connected with shell/terminal :)


I wanted this to be a two part process where I could first see the proposed changes, and then execute them if needed

1
find . -name '*Foo*' | awk '{print("mv "$1" "$1)}' | sed 's/Foo/Bar/2'

What this little nugget does is search in the directory for any directory and/or file that has *foo* in it (that is the -name ‘*foo* part), then uses awk to construct the mv commands, and then uses sed to replace Foo with Bar but only for the second match.
The output would look something like this:

1
2
3
mv x/Foo x/Bar
mv x/Foo.php x/Bar.php
mv x/y/Foo x/y/Bar



If you are happy with the result, you can then execute it by piping it to /bin/sh (or any other shell variant)

1
find . -name '*Foo*' | awk '{print("mv "$1" "$1)}' | sed 's/Foo/Bar/2' | /bin/sh



And there you have it, files and directories are renamed.

Tags: , , , , , , ,

Search and replace text in files from shell

Recently I needed to reuse some files by replacing certain words in them.
I started doing it manually, but as you can imagine it is a tedious, lengthy and error prone process.
There had to be a better way to do it.

As I have learned in recent months, there usually is and is usually connected with shell/terminal :)

1
find . -type f -print0 | xargs -0 -n 1 sed -i -e 's/Foo/Bar/g'

What this little nugget does is search the directory you are in (the . after the find, you can specify one or more directories to search in if you need to), for files (the -type f part), and send their path/names to the std output which will be received by sed to edit files in place searching for Foo and replacing it with Bar.


This process will leave backup files that will have an ‘-e’ appended after the extension, while nice thing to have, for my needs and purposes it is junk, so it is better to remove them

1
find . -type f -name '*-e' -exec rm -rf {} \;

This command will search for all the files that have -e at the end, and will execute rm -rf for them.


And there you have it, contents of the files searched and replaced and junk removed.

Tags: , , , , ,

Changing the Jenkins home directory on Ubuntu – take 2

A month ago, Robert blogged about changing the Jenkins CI home directory on Ubuntu.

Only thing I did not like was setting the path directly in the DAEMON_ARGS and excluding the $JENKINS_HOME variable.

It may not be a problem, but just in case, wouldn’t it be better if we just defined the $JENKINS_HOME to point to the new home directory?
That way, any part of the Jenkins that relies on the $JENKINS_HOME will know where to look.

Instead of editing /etc/init.d/jenkins, and modifying it like Robert suggests to

1
DAEMON_ARGS="--name=$NAME --inherit --env=JENKINS_HOME=/home/jenkins --output=$JENKINS_LOG --pidfile=$PIDFILE"

I would suggest editing the /etc/default/jenkins

1
vi /etc/default/jenkins

And changing the $JENKINS_HOME variable (around line 23) to

1
2
# jenkins home location
JENKINS_HOME=/home/jenkins

Then restart the Jenkins with usual

1
/etc/init.d/jenkins start

Tags: , ,

Symfony2, Sonata Admin Bundle and file uploads

I am writing a bundle that needs to have a file uploaded to server and later served, I am also using Sonata Admin bundle.

First thing I needed to understand is that I will need an additional variable in my entity that will be used for the FileType object and that variable WILL NOT BE MAPPED to doctrine.

Class is shorten for brevity

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
class Product
{
  protected $id;
...
  protected $imageName;
 
  protected $file;
...
  public function getAbsolutePath()
  {
      return null === $this->imageName ? null : $this->getUploadRootDir().'/'.$this->imageName;
  }

  public function getWebPath()
  {
    return null === $this->imageName ? null : $this->getUploadDir().'/'.$this->imageName;
  }

  protected function getUploadRootDir($basepath)
  {
    // the absolute directory path where uploaded documents should be saved
    return $basepath.$this->getUploadDir();
  }

  protected function getUploadDir()
  {
    // get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
    return 'uploads/products';
  }

  public function upload($basepath)
  {
    // the file property can be empty if the field is not required
    if (null === $this->file) {
        return;
    }
   
    if (null === $basepath) {
        return;
    }    
   
    // we use the original file name here but you should
    // sanitize it at least to avoid any security issues

    // move takes the target directory and then the target filename to move to
    $this->file->move($this->getUploadRootDir($basepath), $this->file->getClientOriginalName());

    // set the path property to the filename where you'ved saved the file
    $this->setImageName($this->file->getClientOriginalName());

    // clean up the file property as you won't need it anymore
    $this->file = null;
  }
}

As you can see it is pretty straight forward and you can see it in the Manual as well.

I made a few changes to the way path is generated, I did not like the way it was done in the book, so I pass the base dir path to the class, easier for testing, and less hassle if it needs to be implemented elsewhere.

So far, so good, nothing especially new here.

It’s time for the magic trick :)

Class shortened for brevity

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
class Product extends Admin {
...
  protected function configureFormFields(FormMapper $formMapper) {
    $formMapper
            ->with('General')
...
            ->add('file', 'file', array('required' => false))
...
            ->end()
    ;
  }
...
  public function prePersist($product) {
    $this->saveFile($product);
  }

  public function preUpdate($product) {
    $this->saveFile($product);
  }
 
  public function saveFile($product) {
    $basepath = $this->getRequest()->getBasePath();
    $product->upload($basepath);    
  }
}

What I did not understand at first is that I can use any field type defined in Symfony2 not only those types defined by the Sonata Admin Bundle.

Once I had an a-ha moment, I just added the file type (line 8, second parameter) and done, I had a nice file upload field displayed.

Sonata admin bundle exposes the pre/post persist and pre/post update calls for us to use.

Because we need the file moved and named BEFORE persisting the entity to the DB we need to use the pre* calls,
prePersist call for the Create and preUpdate call for the Update/Edit.

With that I had a final piece of the puzzle.

I added a function that would take the $product param (which is a Product Entity), so that we do not have code duplication.
The base dir path is retrieved from the request, and passed into the upload function of the Product Entity.

Once you hit save button on the form, the appropriate pre* call will be made, file will be uploaded and moved to your location under the web directory, and the entity will be persisted into the db.

Tags: , , ,

Custom Repository with DIC in Symfony2

I am currently working on some Symfony2 bundles, I needed a custom repository to house hold my custom queries, that part is easy with sf2, and quite nicely explained in the Manual.

For brevity, lets setup the CustomRepository

1
2
3
4
5
6
7
namespace Code4Hire\NewBundle\Repository;

use Doctrine\ORM\EntityRepository;

class CustomRepository extends EntityRepository
{
}

And there you have it, your custom repository that will have access to all the methods offered by default, and you can setup all your custom queries as well.

To use it, in your controller you would use

1
2
3
$em = $this->getDoctrine()->getEntityManager();
$custom = $em->getRepository('Code4HireNewBundle:Custom')
            ->findAllOrderedByName();

Which is fine and dandy, but also fugly as hell.

If only there was a way to make a simpler call…

Symfony2 has inversion of control, and dependency injection, sweet, now I can wire it in and let the framework handle it.

Here is how to do it, in your Resources dir, create/edit the services.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="code4_hire_new.entity_manager" alias="doctrine.orm.default_entity_manager" />

        <service id="code4_hire_new.repository.custom"
                factory-service="doctrine.orm.default_entity_manager"
                factory-method="getRepository"
                class="Code4Hire\NewBundle\Repository\Custom" >
            <argument>Code4Hire\NewBundle\Entity\Custom</argument>
        </service>
    </services>

</container>

On line 8 the entity manager service is defined, so the DIC knows what it is, and can use it when needed.

On line 10 our custom repository is defined, and this is where the fun happen.

On line 11 we basically say that our repository will use the entity manager we defined on line 8 as factory service.

On line 12, we tell the DIC to call the getRepository method of the factory service we defined on line 11.

On line 13, we tell the DIC to use the our CustomRepository class to resolve the service.

On line 14, we tell the DIC to use the entity Code4Hire\NewBundle\Repository\Custom as a parameter for the factory method.

And finally, we need to modify our entity mapping xml, i’ll shorten it for brevity.

1
2
3
...
    <entity name="Code4Hire\NewBundle\Entity\Custom" table="custom" repository-class="Code4Hire\NewBundle\Repository\Custom" >
...

Note the repository-class=”Code4Hire\NewBundle\Repository\Custom”, we are explicitly telling the doctrine to use the CustomRepository as the repository class for this entity, this will in turn allow the DIC resolve it properly and to expose all of the custom queries in the CustomRepository class, tip to Mr. Basic for pointing it out

Well, thats quite a lot of things going here, read it a few times and let it sink in, then proceed to the last part.

Time to show what the hubbub is all about.

To call our newly setup custom repository service in dic, all we have to do in our controller is:

1
2
3
4
5
6
7
8
9
10
    public function indexAction()
    {
        $customRepository = $this->get('code4_hire_new.repository.custom');

        $customs = $customRepository->callYourCustomMethod();

        return $this->render('Code4HireNewBundle:Default:index.html.twig', array(
            'customs' => $customs
        ));
    }

On line 3, the custom repository is called by its ID defined in the services.xml, DIC does its work and your repository is instantiated.
Then you proceed to use it as usual.

This looks much cleaner and nicer doesn’t it.

Tags: , , , , ,

Getting Flash to work in Chrome on Ubuntu 10.04 LTS 64bit

This is a digest from a bunch of information from the web.
If Flash is not working in your Chrome browser you may need to create the following symlink to get it working

1
sudo ln -s /usr/lib64/mozilla/plugins-wrapped/nswrapper_32_64.libflashplayer.so /usr/lib/mozilla/plugins/

As usual YMMV, have fun.