SlideShare una empresa de Scribd logo
1 de 44
PHP on the Web and Desktop
     Webservices and PHP-GTK
Whoami

•   http://elizabethmariesmith.com
•   Work at http://omniti.com
•   PHP-GTK
•   PECL cairo
•   WinGui
•   Bad bad bad things to PHP (on windows)
•   Twitter @auroraeosrose
•   IRC auroraeosrose
Community Heckling

• On twitter #zendcon
• Bonus points if you start tweeting with the app
• IRC is open, I can see backlog – constructive
  criticism is good

• Comment on http://joind.in/talk/view/952
• No comments on hair, clothes, or my fat belly –
  constructive criticism is welcome ;)
The Desktop Is Dead?

• If the desktop is dead… what is this browser
  thing you use everyday?

• What is really happening?

• RIA? WTF is RIA?
RIA

      Rich Internet Applications

web applications that have most of the
       characteristics of desktop
  applications, typically delivered by
 way of standards based web browser
     plug-ins or independently via
    sandboxes or virtual machines
                                   (wikipedia)
Webservices
• API’s you can talk to using HTTP
• Extend the power of the web
  anywhere and everywhere
PHP
• Ability to talk HTTP natively
  (streams) or with several
  extensions (pecl_http and curl)
• Lots of code already written for
  common problems involving web
PHP-GTK
• Cross platform widget toolkit for
  desktop programs
• Native look and feel for all
  platforms – this includes Windows
  and Mac
• Reuse all the code you already
  have for your models
Why the desktop?
•   People aren’t always online
•   Some things would be bandwidth prohibitive
•   Some data is too sensitive
•   Embedded OSs have issues
•   Browsers are just Quirky
•   Current Desktop RIA tools are still young
Why use PHP
•   No new language to learn
•   No Compiling
•   Instant Changes
•   Plug into PHP extensions
•   Easy to Use
•   Fast to Write
•   Use existing code
Disadvantages
• Slower than compiled code
• Distribution of runtime
  – New installers help with that
• Distribution of code
  – Phar helps with that
• Code “protection”
  – Can use encoders but anyone with
    enough effort could decode
What is PHP-GTK

• Language bindings for GTK+
  – Cross platform widgeting toolkit, if you use gnome
    you use it
• Multiple “backends” – speaks Windows, Mac
  OSX and X server
• Needs php CLI, php-gtk extension, and gtk
  libraries to run
Installing
• http://oops.opsat.net/doc/install.html
• Windows has Binaries
• Compile it yourself
  – Some distros do bad things to PHP, you
    may get errors
Some points to remember
1. In CLI applications, memory is more
   important than speed
2. Clean up after yourself, unset is your
   friend
3. The OS is handling theming. Unlike
   html, where the design is controlled by
   you. If you manipulate themes, users
   WILL be irritated.
Some points to remember
4. Do not connect directly to a database
   on another machine. This is asking for
   security issues. Instead slap a
   webservice in front of the database for
   your app.
5. Use 5.3 The memory savings you’ll get
   will make it worth the hassle of having
   a special CLI binary just for PHP-GTK
GTK Theory
• Twitter is the new “Hello World”
• Use pre-existing twitter API class for talking to
  twitter via PHP streams
• See how much work we saved? We just write
  the pretty front-end
Widgets
• A building block for GUIs
  – Window
  – Button
  – Menu
• "True" widgets descend from GtkWidget
• Some widgets have their own window
• Some widgets draw on the window
  beneath them instead
Containers
• Specialized widget, holds other widgets
• Can nest containers inside containers
• GTK+ does not do pixel perfect or fixed
  positioning, instead it uses packing
• Packing defines the size of the widgets
Signals
• Event driven, not line by line
• Emits "signals" that have a default
  handler and can have other callbacks
  attached
• Different widgets can have the same
  signal with a different meaning
Main Loop
• No code will run after calling Gtk::main()
  until you call Gtk::main_quit()
• The loop sits around until something
  happens that emits a signal or event
• Long running script as opposed to quick
  set up and tear down
On to Code
• http://gtk.php.net
• http://elizabethmariesmith.com/slides
A note on settings

• Store settings in user defined location
• Xml or ini files are good formats, can also use
  sqlite for lots of settings
• Limited by user permissions for reading and
  writing
Windows – the GTK kind
• Usually you have one main
  window
• You can create pretty splash
  screens if desired
• Windows can be grouped, and can
  be either a parent or transient for
  another window
SplashScreen Example
<?php
$window = new GtkWindow();
$window->set_resizable(false);
$window->set_decorated(false);
$window->set_skip_taskbar_hint(true);
$window->set_skip_pager_hint(true);
$window->set_type_hint(Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN);
$pixbuf = GdkPixbuf::new_from_file($image);
list($pixmap, $mask) = $pixbuf->render_pixmap_and_mask();
list($width, $height) = $pixmap->get_size();
$window->set_app_paintable(true);
$window->set_size_request($width, $height);$window->realize();
if($mask instanceof GdkPixmap) {
     $window->shape_combine_mask($mask, 0, 0);
}
$window->window->set_back_pixmap($pixmap, false);
Create our Main Window
• If we close it, the program ends
• Can set pretty icons for the corner
  and stuff a single widget inside
• You can’t see any widgets until you
  show them
<?php
class Php_Gtk_Twitter_Client extends GtkWindow {

     public function __construct() {
         parent::__construct();
         $this->set_icon($this->render_icon(
            Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG));
         $this->set_size_request(300, 500);
         $this->set_title('PHP-GTK Twitter Client');
         $this->connect_simple('destroy', array('Gtk', 'main_quit'));
     }
}
$window = new Php_Gtk_Twitter_Client;
$window->show_all();
Gtk::main();
A word on Glade/Gtkbuilder
   • Design your layouts in an editor
     (glade3)
   • Save them as xml files
   • Load them via special methods in
     PHP-GTK
   • Automatically generated widgets
Specialty Widgets and Events

• Events are lower level things that happen
• You can attach to events like you attach to
  signals
• Lots of specialty widgets to do fancy stuff, from
  infobars to aboutdialogs to statusicons – and
  that’s not counting extensions
Minimize to the Tray
• Attach to the minimize event (this
  is not as easy as it looks)
• Attach to the activate event
• Make them act differently then
  they normally would
<?php
   public function activate_window($icon, $window) {
       if ($window->is_visible()) {
           $window->hide();
       } else {
           $window->deiconify();
           $window->show();
       }
     }
$this->statusicon->connect('activate', array($this->statusicon,
                     'activate_window'), $this);
$this->set_skip_taskbar_hint(true);
$this->connect('window-state-event', array($this, 'minimize_to_tray'));
public function minimize_to_tray($window, $event) {
  if ($event-
    >changed_mask == Gdk::WINDOW_STATE_ICONIFIED &&
        $event-
    >new_window_state & Gdk::WINDOW_STATE_ICONIFIED) {
            $window->hide();
   }
   return true; //stop bubbling
 }
• I really don’t believe in wheel inventing , especially when I have little
  experience with the subject. However there is an issue – every existing
  twitter API uses curl – and I don’t want to depend on an extension that
  isn’t always installed


  protected function process($url, $date = 0, $type = 'GET', $data = null) {
            // add caching header
            $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date);

            $options = array(
                   'http' => array(
                          'method' => $type,
                          'header' => $this->headers)
                   );
            if (!is_null($data)) {
                   $options['http']['content'] = http_build_query($data);
            }
            $context = stream_context_create($options);
            if ($this->username && $this->password) {
                   $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password)
                   . '@twitter.com/';
            } else {
                   $base = 'http://twitter.com/';
            }
            set_error_handler(array($this,'swallow_error'));
            $string = file_get_contents($base . $url, false, $context);
            restore_error_handler();
            return json_decode($string);
       }
TreeView Madness
• You will use lots of treeviews
• Treeviews work with two
  components, a view and model
• TextViews work in a similar
  manner
• TreeViews have columns with
  renderers
$store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING,
  Gobject::TYPE_STRING, Gobject::TYPE_LONG, GObject::TYPE_STRING,
  GObject::TYPE_BOOLEAN, GObject::TYPE_STRING, Gobject::TYPE_LONG);
     $store->set_sort_column_id(7, Gtk::SORT_DESCENDING);
    $list = $this->twitter->get_public_timeline();
    // stuff the store
    foreach($list as $object) {
           $store->append(array(null, $object->user->profile_image_url, $object->user->name,
                 $object->user->id, $object->text, $object->favorited, $object->created_at,
                 $object->id));
    }
    $this->treeview = new GtkTreeView($store);
    $picture_renderer = new GtkCellRendererPixbuf();
    $picture_column = new GtkTreeViewColumn('Picture', $picture_renderer, 'pixbuf', 0);
    $picture_column->set_cell_data_func($picture_renderer, array($this, 'show_user'));
    $this->treeview->append_column($picture_column);

$message_renderer = new GtkCellRendererText();
    $message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD);
    $message_renderer->set_property('wrap-width', 200);
    $message_renderer->set_property('width', 10);
    $message_column = new GtkTreeViewColumn('Message', $message_renderer);
    $message_column->set_cell_data_func($message_renderer,
  array($this, 'message_markup'));
    $this->treeview->append_column($message_column);
    $this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE);
Formatting Callbacks
public function show_user($column, $cell, $store, $position) {
                 $pic = $store->get_value($position, 1);
                 $name = $this->temp . md5($pic);
                 if (isset($this->pic_queue[$name])) {
                       return;
                 } elseif (isset($this->pic_cached[$name])) {
                       $store = $this->treeview->get_model();
                       if (is_null($store->get_value($position, 0))) {
                              $pixbuf = GdkPixbuf::new_from_file($name . '.jpg');
                              $store->set($position, 0, $pixbuf);
                              $cell->set_property('pixbuf', $pixbuf);
                       }
                       return;
                 }
                 $this->pic_queue[$name] = array('name' => $name, 'url' => $pic,
                       'pos' => $position, 'cell' => $cell);
                 if (empty($this->load_images_timeout)) {
                       $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue'));
                 }
           }
          public function message_markup($column, $cell, $store, $position) {
              $user = utf8_decode($store->get_value($position, 2));
              $message = utf8_decode($store->get_value($position, 4));
              $time = $this->distance($store->get_value($position, 6));
                 $message = htmlspecialchars_decode($message, ENT_QUOTES);
                 $message = str_replace(array('@' . $user, '&nbsp;', '&'), array('<span foreground="#FF6633">@' .
              $user . '</span>', ' ', '&amp;'), $message);
                 $cell->set_property('markup', "<b>$user</b>:n$messagen<small>$time</small>");
          }
Packing Fun
•   GTK is not “pixel perfect”
•   Packing is not as hard as it looks
•   Containers can hold one or many
•   Remember that a container
    expands to the size of it’s contents
$vbox = new GtkVBox();
$this->add($vbox);
$vbox->pack_start($tb, false, false);
$vbox->pack_start($scrolled);
$vbox->pack_start($this->statusbar, false, false);
Dialogs
• Dialogs are cool
• Dialogs are usually modal, but not
  always
• Dialogs can be very general or
  very specific
• You can put anything inside one
class Php_Gtk_Twitter_Login_Dialog extends GtkDialog {
            protected $emailentry;
            protected $passwordentry;
            public function __construct($parent) {
                parent::__construct('Login to Twitter', $parent, Gtk::DIALOG_MODAL,
                      array(
                                  Gtk::STOCK_OK, Gtk::RESPONSE_OK,
                                  Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL));
                $table = new GtkTable();
                $email = new GtkLabel('Email:');
                $table->attach($email, 0, 1, 0, 1);
                $password = new GtkLabel('Password:');
                $table->attach($password, 0, 1, 1, 2);
                $this->emailentry = new GtkEntry();
                $table->attach($this->emailentry, 1, 2, 0, 1);
                $this->passwordentry = new GtkEntry();
                $table->attach($this->passwordentry, 1, 2, 1, 2);
                $this->passwordentry->set_visibility(false);
                $this->vbox->add($table);
                $this->errorlabel = new GtkLabel();
                $this->vbox->add($this->errorlabel);
                $this->show_all();
            }
            public function check_login($twitter) {
                  $this->errorlabel->set_text('');
                  $email = $this->emailentry->get_text();
                  $password = $this->passwordentry->get_text();
                  if (empty($password) || empty($password)) {
                        $this->errorlabel->set_markup(
 '<span color="red">Name and Password must be entered</span>');
                        return false;
                  }
                  if ($twitter->login($email, $password)) {
                        return true;
                  } else {
                        $this->errorlabel->set_markup(
             '<span color="red">Authentication Error</span>');
                        return false;
                  }
            }
      }
if (!empty($this->load_images_timeout)) {
   Gtk::timeout_remove($this->load_images_timeout);
   $readd = true;
}
Gtk::timeout_remove($this->public_timeline_timeout);
Entering Data
• GTKEntry
• Basic Data Entry – activates on
  return, can set maximum length
  allowed
• Simple label for messages – could
  use a dialog or other method of
  informing the user
// Create an update area
         $this->updateentry = new GtkEntry();
         $this->updateentry->set_max_length(140);
         $this->updateentry->set_sensitive(false);
         $this->updateentry->connect('activate',
           array($this, 'send_update'));
         $this->entrystatus = new GtkLabel();




public function send_update($entry) {
      if ($this->twitter->send($entry->get_text())) {
           $this->entrystatus->set_text('Message Sent');
           $this->update_timeline();
           $this->updateentry->set_text('');
     } else {
           $this->entrystatus->
             set_markup(‘<span color="red">Error Sending Message - Try Again</span>');
     }
}
Resources
•   Slides
    • http://elizabethmariesmith.com/slides
•   Code
    • http://elizabethmariesmith.com/slides/php-gtk-twitter.zip
•   GTK docs
    • http://gtk.org
    • http://pygtk.org
    • http://gtk.php.net/docs.php
    • http://kksou.com
    • http://oops.opsat.net
    • http://php-gtk.eu
                                 THANKS

Más contenido relacionado

La actualidad más candente

Understanding PHP objects
Understanding PHP objectsUnderstanding PHP objects
Understanding PHP objects
julien pauli
 
Auto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK NodesAuto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK Nodes
nihiliad
 
Aura Project for PHP
Aura Project for PHPAura Project for PHP
Aura Project for PHP
Hari K T
 
Facebook的缓存系统
Facebook的缓存系统Facebook的缓存系统
Facebook的缓存系统
yiditushe
 

La actualidad más candente (20)

Writing and using php streams and sockets tek11
Writing and using php streams and sockets   tek11Writing and using php streams and sockets   tek11
Writing and using php streams and sockets tek11
 
Understanding PHP objects
Understanding PHP objectsUnderstanding PHP objects
Understanding PHP objects
 
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
 
Zephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensionsZephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensions
 
Scaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
Scaling Symfony2 apps with RabbitMQ - Symfony UK MeetupScaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
Scaling Symfony2 apps with RabbitMQ - Symfony UK Meetup
 
Anatomy of a reusable module
Anatomy of a reusable moduleAnatomy of a reusable module
Anatomy of a reusable module
 
Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)
 
Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018
 
PSGI and Plack from first principles
PSGI and Plack from first principlesPSGI and Plack from first principles
PSGI and Plack from first principles
 
Auto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK NodesAuto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK Nodes
 
Php security3895
Php security3895Php security3895
Php security3895
 
Doctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPDoctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHP
 
Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019Let's write secure drupal code! - Drupal Camp Pannonia 2019
Let's write secure drupal code! - Drupal Camp Pannonia 2019
 
Intro to-puppet
Intro to-puppetIntro to-puppet
Intro to-puppet
 
Puppet @ Seat
Puppet @ SeatPuppet @ Seat
Puppet @ Seat
 
Aura Project for PHP
Aura Project for PHPAura Project for PHP
Aura Project for PHP
 
Facebook的缓存系统
Facebook的缓存系统Facebook的缓存系统
Facebook的缓存系统
 
Cross platform php
Cross platform phpCross platform php
Cross platform php
 
Modern Perl
Modern PerlModern Perl
Modern Perl
 
Replacing "exec" with a type and provider: Return manifests to a declarative ...
Replacing "exec" with a type and provider: Return manifests to a declarative ...Replacing "exec" with a type and provider: Return manifests to a declarative ...
Replacing "exec" with a type and provider: Return manifests to a declarative ...
 

Similar a Php on the Web and Desktop

Twig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHPTwig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHP
Fabien Potencier
 
Php Inside - confoo 2011 - Derick Rethans
Php Inside -  confoo 2011 - Derick RethansPhp Inside -  confoo 2011 - Derick Rethans
Php Inside - confoo 2011 - Derick Rethans
Bachkoutou Toutou
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
Hugo Hamon
 

Similar a Php on the Web and Desktop (20)

[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules
[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules
[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
 
"Xapi-lang For declarative code generation" By James Nelson
"Xapi-lang For declarative code generation" By James Nelson"Xapi-lang For declarative code generation" By James Nelson
"Xapi-lang For declarative code generation" By James Nelson
 
Html5 Overview
Html5 OverviewHtml5 Overview
Html5 Overview
 
Twig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHPTwig, the flexible, fast, and secure template language for PHP
Twig, the flexible, fast, and secure template language for PHP
 
Django at Scale
Django at ScaleDjango at Scale
Django at Scale
 
Staying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHPStaying Sane with Drupal NEPHP
Staying Sane with Drupal NEPHP
 
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
jQuery Makes Writing JavaScript Fun Again (for HTML5 User Group)
 
Php Inside - confoo 2011 - Derick Rethans
Php Inside -  confoo 2011 - Derick RethansPhp Inside -  confoo 2011 - Derick Rethans
Php Inside - confoo 2011 - Derick Rethans
 
Drupal 8 - Core and API Changes
Drupal 8 - Core and API ChangesDrupal 8 - Core and API Changes
Drupal 8 - Core and API Changes
 
1.6 米嘉 gobuildweb
1.6 米嘉 gobuildweb1.6 米嘉 gobuildweb
1.6 米嘉 gobuildweb
 
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
 
Pecl Picks
Pecl PicksPecl Picks
Pecl Picks
 
Everything is Awesome - Cutting the Corners off the Web
Everything is Awesome - Cutting the Corners off the WebEverything is Awesome - Cutting the Corners off the Web
Everything is Awesome - Cutting the Corners off the Web
 
PHP BASIC PRESENTATION
PHP BASIC PRESENTATIONPHP BASIC PRESENTATION
PHP BASIC PRESENTATION
 
Becoming a better WordPress Developer
Becoming a better WordPress DeveloperBecoming a better WordPress Developer
Becoming a better WordPress Developer
 
Drupal Theme Development - DrupalCon Chicago 2011
Drupal Theme Development - DrupalCon Chicago 2011Drupal Theme Development - DrupalCon Chicago 2011
Drupal Theme Development - DrupalCon Chicago 2011
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 
Staying Sane with Drupal (A Develper's Survival Guide)
Staying Sane with Drupal (A Develper's Survival Guide)Staying Sane with Drupal (A Develper's Survival Guide)
Staying Sane with Drupal (A Develper's Survival Guide)
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - Tryout
 

Más de Elizabeth Smith

Security is not a feature
Security is not a featureSecurity is not a feature
Security is not a feature
Elizabeth Smith
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
Elizabeth Smith
 

Más de Elizabeth Smith (20)

Welcome to the internet
Welcome to the internetWelcome to the internet
Welcome to the internet
 
Database theory and modeling
Database theory and modelingDatabase theory and modeling
Database theory and modeling
 
Taming the resource tiger
Taming the resource tigerTaming the resource tiger
Taming the resource tiger
 
Modern sql
Modern sqlModern sql
Modern sql
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Taming the resource tiger
Taming the resource tigerTaming the resource tiger
Taming the resource tiger
 
Php internal architecture
Php internal architecturePhp internal architecture
Php internal architecture
 
Taming the tiger - pnwphp
Taming the tiger - pnwphpTaming the tiger - pnwphp
Taming the tiger - pnwphp
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Php’s guts
Php’s gutsPhp’s guts
Php’s guts
 
Lexing and parsing
Lexing and parsingLexing and parsing
Lexing and parsing
 
Hacking with hhvm
Hacking with hhvmHacking with hhvm
Hacking with hhvm
 
Security is not a feature
Security is not a featureSecurity is not a feature
Security is not a feature
 
Using unicode with php
Using unicode with phpUsing unicode with php
Using unicode with php
 
Mentoring developers-php benelux-2014
Mentoring developers-php benelux-2014Mentoring developers-php benelux-2014
Mentoring developers-php benelux-2014
 
Using unicode with php
Using unicode with phpUsing unicode with php
Using unicode with php
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
 
Mentoring developers
Mentoring developersMentoring developers
Mentoring developers
 
Do the mentor thing
Do the mentor thingDo the mentor thing
Do the mentor thing
 

Último

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
giselly40
 

Último (20)

Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 

Php on the Web and Desktop

  • 1. PHP on the Web and Desktop Webservices and PHP-GTK
  • 2. Whoami • http://elizabethmariesmith.com • Work at http://omniti.com • PHP-GTK • PECL cairo • WinGui • Bad bad bad things to PHP (on windows) • Twitter @auroraeosrose • IRC auroraeosrose
  • 3. Community Heckling • On twitter #zendcon • Bonus points if you start tweeting with the app • IRC is open, I can see backlog – constructive criticism is good • Comment on http://joind.in/talk/view/952 • No comments on hair, clothes, or my fat belly – constructive criticism is welcome ;)
  • 4. The Desktop Is Dead? • If the desktop is dead… what is this browser thing you use everyday? • What is really happening? • RIA? WTF is RIA?
  • 5. RIA Rich Internet Applications web applications that have most of the characteristics of desktop applications, typically delivered by way of standards based web browser plug-ins or independently via sandboxes or virtual machines (wikipedia)
  • 6. Webservices • API’s you can talk to using HTTP • Extend the power of the web anywhere and everywhere
  • 7. PHP • Ability to talk HTTP natively (streams) or with several extensions (pecl_http and curl) • Lots of code already written for common problems involving web
  • 8. PHP-GTK • Cross platform widget toolkit for desktop programs • Native look and feel for all platforms – this includes Windows and Mac • Reuse all the code you already have for your models
  • 9. Why the desktop? • People aren’t always online • Some things would be bandwidth prohibitive • Some data is too sensitive • Embedded OSs have issues • Browsers are just Quirky • Current Desktop RIA tools are still young
  • 10. Why use PHP • No new language to learn • No Compiling • Instant Changes • Plug into PHP extensions • Easy to Use • Fast to Write • Use existing code
  • 11. Disadvantages • Slower than compiled code • Distribution of runtime – New installers help with that • Distribution of code – Phar helps with that • Code “protection” – Can use encoders but anyone with enough effort could decode
  • 12. What is PHP-GTK • Language bindings for GTK+ – Cross platform widgeting toolkit, if you use gnome you use it • Multiple “backends” – speaks Windows, Mac OSX and X server • Needs php CLI, php-gtk extension, and gtk libraries to run
  • 13. Installing • http://oops.opsat.net/doc/install.html • Windows has Binaries • Compile it yourself – Some distros do bad things to PHP, you may get errors
  • 14. Some points to remember 1. In CLI applications, memory is more important than speed 2. Clean up after yourself, unset is your friend 3. The OS is handling theming. Unlike html, where the design is controlled by you. If you manipulate themes, users WILL be irritated.
  • 15. Some points to remember 4. Do not connect directly to a database on another machine. This is asking for security issues. Instead slap a webservice in front of the database for your app. 5. Use 5.3 The memory savings you’ll get will make it worth the hassle of having a special CLI binary just for PHP-GTK
  • 16. GTK Theory • Twitter is the new “Hello World” • Use pre-existing twitter API class for talking to twitter via PHP streams • See how much work we saved? We just write the pretty front-end
  • 17. Widgets • A building block for GUIs – Window – Button – Menu • "True" widgets descend from GtkWidget • Some widgets have their own window • Some widgets draw on the window beneath them instead
  • 18. Containers • Specialized widget, holds other widgets • Can nest containers inside containers • GTK+ does not do pixel perfect or fixed positioning, instead it uses packing • Packing defines the size of the widgets
  • 19. Signals • Event driven, not line by line • Emits "signals" that have a default handler and can have other callbacks attached • Different widgets can have the same signal with a different meaning
  • 20. Main Loop • No code will run after calling Gtk::main() until you call Gtk::main_quit() • The loop sits around until something happens that emits a signal or event • Long running script as opposed to quick set up and tear down
  • 21. On to Code • http://gtk.php.net • http://elizabethmariesmith.com/slides
  • 22. A note on settings • Store settings in user defined location • Xml or ini files are good formats, can also use sqlite for lots of settings • Limited by user permissions for reading and writing
  • 23. Windows – the GTK kind • Usually you have one main window • You can create pretty splash screens if desired • Windows can be grouped, and can be either a parent or transient for another window
  • 24. SplashScreen Example <?php $window = new GtkWindow(); $window->set_resizable(false); $window->set_decorated(false); $window->set_skip_taskbar_hint(true); $window->set_skip_pager_hint(true); $window->set_type_hint(Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN); $pixbuf = GdkPixbuf::new_from_file($image); list($pixmap, $mask) = $pixbuf->render_pixmap_and_mask(); list($width, $height) = $pixmap->get_size(); $window->set_app_paintable(true); $window->set_size_request($width, $height);$window->realize(); if($mask instanceof GdkPixmap) { $window->shape_combine_mask($mask, 0, 0); } $window->window->set_back_pixmap($pixmap, false);
  • 25. Create our Main Window • If we close it, the program ends • Can set pretty icons for the corner and stuff a single widget inside • You can’t see any widgets until you show them
  • 26. <?php class Php_Gtk_Twitter_Client extends GtkWindow { public function __construct() { parent::__construct(); $this->set_icon($this->render_icon( Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG)); $this->set_size_request(300, 500); $this->set_title('PHP-GTK Twitter Client'); $this->connect_simple('destroy', array('Gtk', 'main_quit')); } } $window = new Php_Gtk_Twitter_Client; $window->show_all(); Gtk::main();
  • 27. A word on Glade/Gtkbuilder • Design your layouts in an editor (glade3) • Save them as xml files • Load them via special methods in PHP-GTK • Automatically generated widgets
  • 28. Specialty Widgets and Events • Events are lower level things that happen • You can attach to events like you attach to signals • Lots of specialty widgets to do fancy stuff, from infobars to aboutdialogs to statusicons – and that’s not counting extensions
  • 29. Minimize to the Tray • Attach to the minimize event (this is not as easy as it looks) • Attach to the activate event • Make them act differently then they normally would
  • 30. <?php public function activate_window($icon, $window) { if ($window->is_visible()) { $window->hide(); } else { $window->deiconify(); $window->show(); } } $this->statusicon->connect('activate', array($this->statusicon, 'activate_window'), $this); $this->set_skip_taskbar_hint(true); $this->connect('window-state-event', array($this, 'minimize_to_tray')); public function minimize_to_tray($window, $event) { if ($event- >changed_mask == Gdk::WINDOW_STATE_ICONIFIED && $event- >new_window_state & Gdk::WINDOW_STATE_ICONIFIED) { $window->hide(); } return true; //stop bubbling }
  • 31. • I really don’t believe in wheel inventing , especially when I have little experience with the subject. However there is an issue – every existing twitter API uses curl – and I don’t want to depend on an extension that isn’t always installed protected function process($url, $date = 0, $type = 'GET', $data = null) { // add caching header $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date); $options = array( 'http' => array( 'method' => $type, 'header' => $this->headers) ); if (!is_null($data)) { $options['http']['content'] = http_build_query($data); } $context = stream_context_create($options); if ($this->username && $this->password) { $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password) . '@twitter.com/'; } else { $base = 'http://twitter.com/'; } set_error_handler(array($this,'swallow_error')); $string = file_get_contents($base . $url, false, $context); restore_error_handler(); return json_decode($string); }
  • 32. TreeView Madness • You will use lots of treeviews • Treeviews work with two components, a view and model • TextViews work in a similar manner • TreeViews have columns with renderers
  • 33. $store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING, Gobject::TYPE_STRING, Gobject::TYPE_LONG, GObject::TYPE_STRING, GObject::TYPE_BOOLEAN, GObject::TYPE_STRING, Gobject::TYPE_LONG); $store->set_sort_column_id(7, Gtk::SORT_DESCENDING); $list = $this->twitter->get_public_timeline(); // stuff the store foreach($list as $object) { $store->append(array(null, $object->user->profile_image_url, $object->user->name, $object->user->id, $object->text, $object->favorited, $object->created_at, $object->id)); } $this->treeview = new GtkTreeView($store); $picture_renderer = new GtkCellRendererPixbuf(); $picture_column = new GtkTreeViewColumn('Picture', $picture_renderer, 'pixbuf', 0); $picture_column->set_cell_data_func($picture_renderer, array($this, 'show_user')); $this->treeview->append_column($picture_column); $message_renderer = new GtkCellRendererText(); $message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD); $message_renderer->set_property('wrap-width', 200); $message_renderer->set_property('width', 10); $message_column = new GtkTreeViewColumn('Message', $message_renderer); $message_column->set_cell_data_func($message_renderer, array($this, 'message_markup')); $this->treeview->append_column($message_column); $this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE);
  • 34. Formatting Callbacks public function show_user($column, $cell, $store, $position) { $pic = $store->get_value($position, 1); $name = $this->temp . md5($pic); if (isset($this->pic_queue[$name])) { return; } elseif (isset($this->pic_cached[$name])) { $store = $this->treeview->get_model(); if (is_null($store->get_value($position, 0))) { $pixbuf = GdkPixbuf::new_from_file($name . '.jpg'); $store->set($position, 0, $pixbuf); $cell->set_property('pixbuf', $pixbuf); } return; } $this->pic_queue[$name] = array('name' => $name, 'url' => $pic, 'pos' => $position, 'cell' => $cell); if (empty($this->load_images_timeout)) { $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue')); } } public function message_markup($column, $cell, $store, $position) { $user = utf8_decode($store->get_value($position, 2)); $message = utf8_decode($store->get_value($position, 4)); $time = $this->distance($store->get_value($position, 6)); $message = htmlspecialchars_decode($message, ENT_QUOTES); $message = str_replace(array('@' . $user, '&nbsp;', '&'), array('<span foreground="#FF6633">@' . $user . '</span>', ' ', '&amp;'), $message); $cell->set_property('markup', "<b>$user</b>:n$messagen<small>$time</small>"); }
  • 35.
  • 36. Packing Fun • GTK is not “pixel perfect” • Packing is not as hard as it looks • Containers can hold one or many • Remember that a container expands to the size of it’s contents
  • 37. $vbox = new GtkVBox(); $this->add($vbox); $vbox->pack_start($tb, false, false); $vbox->pack_start($scrolled); $vbox->pack_start($this->statusbar, false, false);
  • 38. Dialogs • Dialogs are cool • Dialogs are usually modal, but not always • Dialogs can be very general or very specific • You can put anything inside one
  • 39. class Php_Gtk_Twitter_Login_Dialog extends GtkDialog { protected $emailentry; protected $passwordentry; public function __construct($parent) { parent::__construct('Login to Twitter', $parent, Gtk::DIALOG_MODAL, array( Gtk::STOCK_OK, Gtk::RESPONSE_OK, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL)); $table = new GtkTable(); $email = new GtkLabel('Email:'); $table->attach($email, 0, 1, 0, 1); $password = new GtkLabel('Password:'); $table->attach($password, 0, 1, 1, 2); $this->emailentry = new GtkEntry(); $table->attach($this->emailentry, 1, 2, 0, 1); $this->passwordentry = new GtkEntry(); $table->attach($this->passwordentry, 1, 2, 1, 2); $this->passwordentry->set_visibility(false); $this->vbox->add($table); $this->errorlabel = new GtkLabel(); $this->vbox->add($this->errorlabel); $this->show_all(); } public function check_login($twitter) { $this->errorlabel->set_text(''); $email = $this->emailentry->get_text(); $password = $this->passwordentry->get_text(); if (empty($password) || empty($password)) { $this->errorlabel->set_markup( '<span color="red">Name and Password must be entered</span>'); return false; } if ($twitter->login($email, $password)) { return true; } else { $this->errorlabel->set_markup( '<span color="red">Authentication Error</span>'); return false; } } }
  • 40. if (!empty($this->load_images_timeout)) { Gtk::timeout_remove($this->load_images_timeout); $readd = true; } Gtk::timeout_remove($this->public_timeline_timeout);
  • 41. Entering Data • GTKEntry • Basic Data Entry – activates on return, can set maximum length allowed • Simple label for messages – could use a dialog or other method of informing the user
  • 42. // Create an update area $this->updateentry = new GtkEntry(); $this->updateentry->set_max_length(140); $this->updateentry->set_sensitive(false); $this->updateentry->connect('activate', array($this, 'send_update')); $this->entrystatus = new GtkLabel(); public function send_update($entry) { if ($this->twitter->send($entry->get_text())) { $this->entrystatus->set_text('Message Sent'); $this->update_timeline(); $this->updateentry->set_text(''); } else { $this->entrystatus-> set_markup(‘<span color="red">Error Sending Message - Try Again</span>'); } }
  • 43.
  • 44. Resources • Slides • http://elizabethmariesmith.com/slides • Code • http://elizabethmariesmith.com/slides/php-gtk-twitter.zip • GTK docs • http://gtk.org • http://pygtk.org • http://gtk.php.net/docs.php • http://kksou.com • http://oops.opsat.net • http://php-gtk.eu THANKS

Notas del editor

  1. How many people have ever heard “the desktop is dead, everything is online”There have been several recent articles about itEveryone is touting their RIA application that “works like a desktop”If the desktop is dead, and everything is going to be online, why do you keep hearing about this RIA thing?
  2. So what they’re really saying is – web apps that want to act like desktop appsEverybody wants the connection of the internet, but the user experience of a desktop applicationSo how do we join the two?
  3. Name a webservice you use all the time ( I better hear twitter)Can use straight REST, SOAP, xml-rpc (ugh), plus other variants
  4. So how do we take these three things – an application that wants to be on the desktop, not just the web, webservices we hit with http, and PHP which “talks http” fabulously?
  5. Contrary to popular belief, people still work offlineEmbedded OS’s have major issues with memory and bandwidth and websites not working properly with themWho here has had issues with IE ever?And then there’s issues with AIR or silverlight or titanium… they just don’t quite work yet
  6. There are lots of advantages to using PHP to write a desktop app
  7. Speed is the big reason to pick C or something similar – stacked against ruby or python though, that’s the only advantage ;)The code protection, runtime distribution, etc only let’s you win over C or similar languages, not against python or ruby or other $flavor of the dayNote that runtime distribution is going to differ from platform to platformIt’s 13MB for EVERYTHING for windows – php, php-gtk, all the gtk libraries even esoteric stuff, and one or two localizationsMac has a new runtime that doesn’t require X – which is awesome
  8. GTK was Gimp Tool Kit (long long ago)Php-gtk is really php-gtk2 – but php-gtk1 only works on php4 which is dead, so it in esscense is dead as well (good riddance)Your computer does not care about HTML.Your shell does not care about HTML.PHP does not care about HTML.Because of all of this, you should not care about HTML.PHP and GTK does not use HTML.PHP and GTK does not build web pages.PHP and GTK does not build web applications.PHP and GTK builds computer programs.- From bob MajdakThere are other options, but right now none work as well or are as complete as php-gtkPHP-QTWxWidgetsWin\\GuiWinbinder
  9. Do not change the users desired colors and theme unless absolutely necessary and make sure there are ways for the use rto control the changes the OS is handling theming for a reasonYou can’tbe certain users have the sametheme as you, gtkcan have anynumber of themes and look manydifferentwaysThere ishowever, nothingwrongwithofferingusers options – perhaps an option to makeiteasier to use on embeddeddevices, withfingerwidthscrollbars and otherusefultools
  10. It’s trivial to add a webservice for your database, and much safer then allowing access across the network to the dbRemember you can do multiple CLI binaries on a system – one just for php-gtk is fine
  11. We already have a class to manage caching data in an sqlite3 DB via PDO, and a class to manage talking to the Twitter API – reusing code is greatYou can use any PHP code with php-gtk – from Zend Framework to anything else you want
  12. Widgets – think legos, each with a specific shape and specific jobThere are a LOT of gtk widgets, and some “widgets” that aren’t widgets…Brief note about two types of windows – a gtkwindow (the actual widget window) and gdkwindow (what the widget is drawing in)
  13. The contained widgets are container&apos;s children.Containers usually respond to resize events and addition and removal of children by reallocating the available space among its remaining children.
  14. In computer programming, event-driven programming or event-based programming is a programming paradigm in which the flow of the program is determined by events—i.e., sensor outputs or user actions (mouse clicks, key presses) or messages from other programs or threads.Signals are identified by nameMention “events’ as well and bubbling and signal emission order
  15. Different style of programming
  16. TODO: get the installers done and in place, linux installer of bobs
  17. Encrypting settings isn’t going to gain you much, although you can do it if you are really paranoidWhere is the “user defined location”APPDATA for windows and HOME for most other systems (in $_SERVER)There are also some other “special location” – on windows you can get to these through some com tricks
  18. These are the window widgets – there are also gdk windows – those are the areas where the underlying system actually draws onto – they’re both called windows just to confuse the hell out of you
  19. Creating a splash screen means creating a window, hinting it as a splashscreen, telling it not to have edges or taskbar or resize, then taking an image and rendering it as the background – the mask stuff means that it can be a transparent png or the like and have a custom shape
  20. Stuff to know
  21. Using what we just talked aboutLet’s start with a basicgtk window – notice it’s easier to extend the base classConnect the destruction of our main window with stopping theConcepts – signals and connections, the main loop and main_quit, show allNotice if you put PHP after the main(); call it will not be executed until after the main_quit call
  22. Gtkbuilder is preferred, and it’s built in – glade is old and busted – but the editor (glade3) is the same for both, just change the output you want
  23. There are more than 50 widgets, and those widgets have thousands of signals and methods…GTK is HUGE and learning the cool stuff available might take awhile (remember when you started learning PHP?)
  24. The full code shows how to create the status icon and other fun stuff
  25. My own simple twitter class – login, logout, public timeline, own friends timeline, send message Concepts – use PHP code with PHP-GTK, can use other extensions but then they have to be installed
  26. So to show something in a treeview, you create a datastore, stuff in the data, then create a treeview, hook it to the model – then create renderers for each type of data you want to show, put the renderer(s) in a column, and put the column in a treeview (deep breath)
  27. So to show something in a treeview, you create a datastore, stuff in the data, then create a treeview, hook it to the model – then create renderers for each type of data you want to show, put the renderer(s) in a column, and put the column in a treeview (deep breath)
  28. First version of the app with the public timeline
  29. So to show something in a treeview, you create a datastore, stuff in the data, then create a treeview, hook it to the model – then create renderers for each type of data you want to show, put the renderer(s) in a column, and put the column in a treeview (deep breath)
  30. Concepts: packing and toolbarsMore gtktimeout
  31. General concept of dialogs – when is a good time for modal dialogs, when is not a good time
  32. Concepts: packing and toolbarsMore gtktimeout
  33. Concepts: packing and toolbarsMore gtktimeout
  34. Add data in with a gtkentry
  35. Concepts: packing and toolbarsMore gtktimeout
  36. Here’s the finished app – then I’ll run it in action
  37. Places to get more information and recruit!!