These are the React Native tricks I’ve found in the past 2 years.

React navigation reset stack navigator

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
/** From stack navigator definision **/
import {
CardStyleInterpolators,
createStackNavigator,
} from '@react-navigation/stack';

const Stack = createStackNavigator();

<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen
name="ScreenA"
component={ScreenA}
options={{
cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS,
}}
/>
<Stack.Screen
name="ScreenB"
component={ScreenB}
options={{
cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS,
}}
/>
<Stack.Screen
name="ScreenC"
component={ScreenC}
options={{
cardStyleInterpolator: CardStyleInterpolators.forVerticalIOS,
gestureEnabled: false,
}}
/>
</Stack.Navigator>

The screen navigation order is ScreenA -> ScreenB -> ScreenC. If we want to navigation from ScreenC and reset the stack to SreenA, we can do the following in ScreenC.

1
2
3
4
5
6
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{name: 'ScreenA'}],
}),
);

Use fetch

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
/** Fetch with timeout **/
export async function fetchWithTimeout(resource, options) {
const {timeout = 10000} = options; // 10 seconds
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(resource, {
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
}

/** General Graphql calls **/
export async function fetchGraphQL(
query,
variables,
timeout = 10000,
) {
// Fetch data from GitHub's GraphQL API:
const response = await fetchWithTimeout('BASE_API_URL', {
method: 'POST',
headers: {
Authorization: `bearer JWT_TOKEN`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: query,
variables,
}),
timeout: timeout,
});

const jsonResponse = await response.json();
// Get the response as JSON
return jsonResponse;
}

/** Real call**/
const response = await fetchGraphQL(
`mutation updateSomething(
$test: String!) {
updateSomething(
test: $test
)
}`,
{
test: 'test',
},
);

How to use Realm

1
2
3
4
5
6
7
8
9
/** Base Realm initiator **/
import Realm from 'realm';
import Test from '../Model/Test';

export default new Realm({
path: 'DB_NAME',
schema: [Test],
schemaVersion: 1,
});
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
import Realm from 'realm';

export default class Test extends Realm.Object {
static schema = {
name: 'Test',
properties: {
ID: {type: 'int', default: 0},
Name: {type: 'string', default: ''},
},
primaryKey: 'ID',
};
}

export function getObjectById(DatabaseService, id) {
const tests = DatabaseService.objects('Test').filtered(
'ID ==[c] $0',
id,
);
if (tests && tests.length === 0) {
return null;
}
return convertRealmObjectToJson(tests[0], Test.schema.properties);
}

export function convertRealmObjectToJson(realmObject, schemaPropertyList) {
const result = {};
const keys = Object.keys(schemaPropertyList);
for (let key of keys) {
result[key] = realmObject[key];
}
return result;
}

How to debug

reactotron-react-native
https://github.com/infinitered/reactotron

Screen auto Rotation

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
/** Screen Component **/
import ScreenStyles from './ScreenStyles';

export default function DeviceScreen(props) {
const [styles, setStyles] = useState(ScreenStyles());
return (
<View
style={styles.container}
onLayout={event => {
setStyles(ScreenStyles());
}}>
</View>
);
}

/** Style component **/
export default function () {
/*
Do some screen detection and do proper styling with screen specific
*/

return StyleSheet.create({
container: {
flex: 1,
},
});
}

Tile swiper with dots

https://github.com/leecade/react-native-swiper
https://github.com/gusgard/react-native-swiper-flatlist

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
import React from 'react';
import {View, StyleSheet} from 'react-native';

export default function PaginationDots({length, activeIndex}) {
return (
<View style={Styles.container}>
{[...Array(length).keys()].map((_, i) => (
<View
key={i}
style={[
Styles.dot,
{
backgroundColor:
i !== activeIndex ? Colors.greenLight : Colors.charcoalGreen,
},
]}
/>
))}
</View>
);
}

const Styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
dot: {
width: 10,
height: 10,
borderRadius: 10 / 2,
borderColor: 'black',
borderWidth: 1,
marginLeft: 5,
},
});

LaunchDarkly integration

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
import LDClient from 'launchdarkly-react-native-client-sdk';

export const LaunchDarklyClient = new LDClient();

export const initLaunchDarkly = async () => {
let config = {
mobileKey: PROD_MOBILE_KEY,
secondaryMobileKeys: {
qa: QA_MOBILE_KEY,
},
},
user = {
key: USER_KEY,
};
try {
await LaunchDarklyClient.configure(config, user, 5.0);
} catch (err) {
console.log('launchDarkly err', err);
}
};

const getBooleanValue = async (environment, key, defaultValue) => {
if (environment === 'QA') {
return await LaunchDarklyClient.boolVariation(key, defaultValue, 'qa');
} else {
return await LaunchDarklyClient.boolVariation(key, defaultValue);
}
};

File sharing

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
import RNFetchBlob from 'react-native-blob-util';
import RNFS from 'react-native-fs';
import Share from 'react-native-share';

export const shareWithIOS = async (
fileUrl,
file_name,
type = 'application/pdf',
) => {
let filePath = null;
const configOptions = {
fileCache: true,
path: RNFetchBlob.fs.dirs.DocumentDir + '/' + file_name,
};
try {
const response = await RNFetchBlob.config(configOptions).fetch(
'GET',
fileUrl,
);
filePath = response.path();
let options = {
type: type,
filename: file_name,
url: filePath,
};
await Share.open(options);
await RNFS.unlink(filePath);
} catch (error) {
console.log('error', error);
}
};

export const shareWithAndroid = async (
fileUrl,
file_name,
type = 'application/pdf',
) => {
let filePath = null;
const configOptions = {
fileCache: true,
path: RNFetchBlob.fs.dirs.DocumentDir + file_name,
};
try {
const response = await RNFetchBlob.config(configOptions).fetch(
'GET',
fileUrl,
);
filePath = response.path();

const base64Data = await response.readFile('base64');
const base64Url = `data:${type};base64,${base64Data}`;
let options = {
filename: file_name,
url: base64Url,
};
await Share.open(options);
await RNFS.unlink(filePath);
} catch (error) {
console.log('error', error);
}
};

Upload aab to PlayStore

1
2
3
4
/** fastlane/AppFile **/
json_key_file("./google-key.json")
package_name("ANDROID_APP_ID")

1
fastlane supply --aab app-release.aab --track alpha

Upload ipa to AppStore

1
xcrun altool --upload-app --type ios --file YOUR_APP.ipa --username "APPLE_ID" --password "APPLE ID PWD"

This is a docker-compose temaplte and file structures for Laravel development.

Docker compose template

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
# docker-compose.yml
version: '3'
services:

#PHP Service
app:
build: ./php
image: homestead/php:7.2
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
volumes:
- LARAVEL_APP_ROOT_PATH:/var/www
- ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- app-network

#Redis service
redis:
image: redis
container_name: redis
restart: unless-stopped
tty: true
ports:
- "6379:6379"
networks:
- app-network

#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "8888:80"
- "443:443"
volumes:
- LARAVEL_APP_ROOT_PATH:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/
networks:
- app-network

#MySQL Service
db:
image: mysql:5.7.22
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
MYSQL_USER: homestead
MYSQL_PASSWORD: secret
SERVICE_TAGS: dev
SERVICE_NAME: mysql
volumes:
- dbdata:/var/lib/mysql/
- ./mysql/my.cnf:/etc/mysql/my.cnf
networks:
- app-network

#phpmyadmin
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
restart: unless-stopped
tty: true
environment:
- PMA_ARBITRARY=1
- PMA_HOST=db
- PMA_PORT=3306
- PMA_USER=homestead
- PMA_PASSWORD=secret
ports:
- 8000:80
volumes:
- /sessions
networks:
- app-network

#socket.io
socket:
image: node:10.16.0
container_name: app-socket
restart: unless-stopped
tty: true
ports:
- 6001:6001
volumes:
- LARAVEL_APP_ROOT_PATH:/var/www
working_dir: /var/www
command: bash -c "npm install && node /var/www/socket.dev.js"
networks:
- app-network

#Docker Networks
networks:
app-network:
driver: bridge
#Volumes
volumes:
dbdata:
driver: local

PHP Stack

In folder ./php
– ./php/api-queue.conf // API queue worker
– ./php/crontab // Cron job
– ./php/Dockerfile // Docker image build
– ./php/local.ini // The PHP config
– ./php/start.sh // Start up services

  • ./php/api-queue.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [program:api-queue]
    command=php /var/www/artisan queue:work --tries=1 --memory=384 --timeout=600 --env=local
    user=www
    numprocs=2
    process_name=%(program_name)s_%(process_num)02d
    autostart=true
    autorestart=true
    startretries=99999
    stderr_logfile=/var/log/api-queue.log
  • ./php/crontab
    1
    2
    * * * * * root /usr/local/bin/php  /var/www/artisan schedule:run --env=local

  • ./php/Dockerfile
    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
    FROM php:7.2-fpm-stretch

    # Set working directory
    WORKDIR /var/www

    # Install dependencies and extensions
    RUN buildDeps=" \
    default-libmysqlclient-dev \
    libbz2-dev \
    libmemcached-dev \
    libsasl2-dev \
    " \
    runtimeDeps=" \
    build-essential \
    mysql-client \
    supervisor \
    cron \
    libpng-dev \
    libjpeg62-turbo-dev \
    locales \
    zip \
    jpegoptim \
    optipng \
    pngquant \
    gifsicle \
    vim \
    unzip \
    curl \
    git \
    libgmp-dev \
    re2c \
    libmhash-dev \
    file \
    libfreetype6-dev \
    libicu-dev \
    libjpeg-dev \
    libldap2-dev \
    libmcrypt-dev \
    libmemcachedutil2 \
    libpng-dev \
    libxml2-dev \
    " \
    && apt-get update && apt-get -y upgrade && apt-get dist-upgrade && DEBIAN_FRONTEND=noninteractive apt-get install -y $buildDeps $runtimeDeps \
    && apt-get install -y libpq-dev \
    && apt-get clean \
    && ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/local/include/ \
    && docker-php-ext-configure gmp \
    && docker-php-ext-install zip exif pcntl bcmath bz2 calendar iconv intl mbstring mysqli opcache pdo_mysql gmp pdo_pgsql pgsql soap zip \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ \
    && docker-php-ext-install gd \
    && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ \
    && docker-php-ext-install ldap \
    && docker-php-ext-install exif \
    && pecl install memcached redis \
    && docker-php-ext-enable memcached.so redis.so \
    && apt-get purge -y --auto-remove $buildDeps \
    && rm -r /var/lib/apt/lists/*

    # Install composer
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

    # Add user for laravel application
    RUN groupadd -g 1000 www \
    && useradd -u 1000 -ms /bin/bash -g www www

    # Set the cron job
    COPY ./crontab /etc/cron.d/cool-task
    RUN chmod 0644 /etc/cron.d/cool-task && crontab /etc/cron.d/cool-task && touch /var/log/cron.log

    # Set the supervisord
    COPY ./api-queue.conf /etc/supervisor/conf.d/api-queue.conf

    # Boost compose install speed
    RUN composer global require hirak/prestissimo

    COPY ./start.sh /usr/local/bin/start.sh
    RUN cat /usr/local/bin/start.sh && chmod +x /usr/local/bin/start.sh

    # Expose port 9000 and start php-fpm server
    EXPOSE 9000
    CMD service supervisor start && service cron start && php-fpm && cron

  • ./php/local.ini
    1
    2
    3
    upload_max_filesize=40M
    post_max_size=40M

  • ./php/start.sh
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bash

    chmod 0644 /etc/cron.d/cool-task
    crontab /etc/cron.d/cool-task
    touch /var/log/cron.log

    service supervisor start
    service cron start
    php-fpm
    cron

NGINX

./nginx/conf.d/app.conf

  • ./nginx/conf.d/app.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    server {
    listen 80;
    index index.php index.html;
    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass app:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
    try_files $uri $uri/ /index.php?$query_string;
    gzip_static on;
    }
    }

MYSQL

  • ./mysql/my.cnf
    1
    2
    3
    4
    [mysqld]
    general_log = 1
    general_log_file = /var/lib/mysql/general.log

Laravel environment file

  • ./api_development_env_files/.env
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    APP_ENV=local
    APP_KEY=abcdf
    APP_URL=http://laravel.api
    DB_HOST=db
    DB_DATABASE=homestead
    DB_USERNAME=homestead
    DB_PASSWORD=secret
    PUSH_ENV=development
    CACHE_DRIVER=redis
    SESSION_DRIVER=file
    BROADCAST_DRIVER=redis
    REDIS_HOST=redis
    JWT_SECRET=JWT_SECRET
    JWT_TTL=262800
    JWT_TTL_REFRESH=262800
    ALLOW_ORIGIN=*
    QUEUE_DRIVER=sync #sqs in production, but sync for staging

We can find a lot of tutorials about how to setup Android CI/CD. But it’s hard for us to setup the iOS CI/CD. Also It’s expensive to buy a Mac computer to run and build iOS app. As we can find Azure Devops provide MacOS build agent, we can build and publish the iOS app on Azure devops for a lot cheaper prices.

Here is a Pipeline example of building iOS app on Azure Devops:

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
pool:
vmImage: 'macos-latest'

steps:
- task: NodeTool@0
inputs:
versionSpec: '10.16.0'
displayName: 'Install Node.js 10.16.0'
- script: npm install
displayName: 'npm install'
- script: npm run lint
displayName: 'Run lint'
# iOS build
- task: InstallAppleCertificate@2
displayName: 'Install Apple distribution certificate'
inputs:
certSecureFile: 'IOS_RELEASE_CERTIFICATE.p12'
certPwd: $(IOS_RELEASE_P12_PASSWORD)
- task: InstallAppleProvisioningProfile@1
displayName: 'Install distribution provisional profile'
inputs:
provProfileSecureFile: 'IOS_RELEASE_PROVISION_PROFILE.mobileprovision'
- script: |
cd ios
pod install
cd ..
displayName: 'Install pod'
- script: |
npm install -g react-native-cli
react-native bundle --entry-file index.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios
displayName: 'Bundle ios'
- task: Xcode@5
displayName: 'Build IPA for Release'
inputs:
actions: 'clean archive'
scheme: 'IOS_SCHEME_FOUND_IN_XCODE'
sdk: 'iphoneos'
configuration: 'Release'
xcWorkspacePath: 'IOS_WORKSPACE_FILE.xcworkspace'
xcodeVersion: 'default' # Options: 8, 9, 10, default, specifyPath
packageApp: true
signingOption: 'manual' # Options: nosign, default, manual, auto
useXcpretty: true # Makes it easier to diagnose build failures
teamId: $(IOS_TEAM_ID)
provisioningProfileUuid: $(IOS_RELEASE_PROVISIONING_PROFILE_UUID)
provisioningProfileName: 'THE_PROVISIONING_PROFILE_NAME_IN_APP_STORE'
archivePath: ios/build/Products/$(IOS_PROJECT_NAME).xcarchive
exportMethod: app-store
exportPath: ios/IPA/distribution
exportOptions: 'plist'
exportOptionsPlist: scripts/ios/exportOptions/exportOptions-dist.plist
- task: PublishPipelineArtifact@1
inputs:
path: $(System.DefaultWorkingDirectory)/ios/IPA
artifact: YOUR_APP_IOS

Here is a Pipeline example of building Android app on Azure Devops:

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
pool:
vmImage: 'macos-latest'

steps:
- task: NodeTool@0
inputs:
versionSpec: '10.16.0'
displayName: 'Install Node.js 10.16.0'
- script: npm install
displayName: 'npm install'
- script: npm run lint
displayName: 'Run lint'
# - script: npm run test
# displayName: 'Run unit tests'
# Android build
- task: DownloadSecureFile@1
name: android_keystore
displayName: 'Download Android keystore'
inputs:
secureFile: 'KEYSTORE_FILE.jks'
- script: |
cd android
./gradlew clean assembleRelease -PBUILD_NAME=$BUILD_NAME -PBUILD_NUMBER=$ANDROID_BUILD_NUMBER -PANDROID_APP_ID=$ANDROID_APP_ID -PMYAPP_RELEASE_STORE_FILE=$(android_keystore.secureFilePath) -PMYAPP_RELEASE_KEY_ALIAS=$ANDROID_KEY_ALIAS -PMYAPP_RELEASE_STORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD -PMYAPP_RELEASE_KEY_PASSWORD=$ANDROID_KEY_PASSWORD
./gradlew bundleRelease -PBUILD_NAME=$BUILD_NAME -PBUILD_NUMBER=$ANDROID_BUILD_NUMBER -PANDROID_APP_ID=$ANDROID_APP_ID -PMYAPP_RELEASE_STORE_FILE=$(android_keystore.secureFilePath) -PMYAPP_RELEASE_KEY_ALIAS=$ANDROID_KEY_ALIAS -PMYAPP_RELEASE_STORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD -PMYAPP_RELEASE_KEY_PASSWORD=$ANDROID_KEY_PASSWORD
cd ..
mkdir android_outputs
cp -R android/app/build/outputs ./android_outputs
sed 's/enableSeparateBuildPerCPUArchitecture\ =\ true/enableSeparateBuildPerCPUArchitecture\ =\ false/g' android/app/build.gradle > ./build.gradle
rm android/app/build.gradle
cp ./build.gradle android/app/build.gradle
cd android
./gradlew clean assembleRelease -PBUILD_NAME=$BUILD_NAME -PBUILD_NUMBER=$ANDROID_BUILD_NUMBER -PANDROID_APP_ID=$ANDROID_APP_ID -PMYAPP_RELEASE_STORE_FILE=$(android_keystore.secureFilePath) -PMYAPP_RELEASE_KEY_ALIAS=$ANDROID_KEY_ALIAS -PMYAPP_RELEASE_STORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD -PMYAPP_RELEASE_KEY_PASSWORD=$ANDROID_KEY_PASSWORD
cd ..
cp android/app/build/outputs/apk/release/app-release.apk ./android_outputs/outputs/apk/release/app-release.apk
displayName: 'Building Android APK and packing distribution bundle'
- task: PublishPipelineArtifact@1
inputs:
path: $(System.DefaultWorkingDirectory)/android_outputs/outputs
artifact: ANDROID_ARTIFACT

Requirement PHP 5.5+

  • Generators are simple iterators.
  • Compute and yield iteration values on-demand without commandeering valuable memory.
  • Iterate in only one direction—forward
  • Can’t iterate the same generator more than once

Create generator

1
2
3
4
5
6
7
8
9
10
<?php
function myGenerator() {
yield 'value1';
yield 'value2';
yield 'value3';
}

foreach (myGenerator() as $yieldedValue) {
echo $yieldedValue, PHP_EOL;
}

Output:

1
2
3
value1
value2
value3

Use generator

Bad to use traditional iterator because it load everything into memory.

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function makeRange($length) {
$dataset = [];
for ($i = 0; $i < $length; $i++) {
$dataset[] = $i;
}
return $dataset;
}
$customRange = makeRange(1000000);
foreach ($customRange as $i) {
echo $i, PHP_EOL;
}

Good to use generator.

1
2
3
4
5
6
7
8
9
<?php
function makeRange($length) {
for ($i = 0; $i < $length; $i++) {
yield $i;
}
}
foreach (makeRange(1000000) as $i) {
echo $i, PHP_EOL;
}

CSV generator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function getRows($file) {
$handle = fopen($file, 'rb');
if ($handle === false) {
throw new Exception();
}
while (feof($handle) === false) {
yield fgetcsv($handle);
}
fclose($handle);
}
foreach (getRows('data.csv') as $row) {
print_r($row);
}

Requirement PHP 5.4+

Create a trait

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
trait Geocodable {
/** @var string */
protected $address;
/** @var \Geocoder\Geocoder */
protected $geocoder;
/** @var \Geocoder\Result\Geocoded */
protected $geocoderResult;

public function setGeocoder(\Geocoder\GeocoderInterface $geocoder)
{
$this->geocoder = $geocoder;
}
public function setAddress($address)
{
$this->address = $address;
}
public function getLatitude()
{
if (isset($this->geocoderResult) === false) {
$this->geocodeAddress();
}
return $this->geocoderResult->getLatitude();
}
public function getLongitude()
{
if (isset($this->geocoderResult) === false) {
$this->geocodeAddress();
}
return $this->geocoderResult->getLongitude();
}
protected function geocodeAddress()
{
$this->geocoderResult = $this->geocoder->geocode($this->address);
return true;
}
}

Use a trait

1
2
3
4
5
6
<?php
class RetailStore
{
use Geocodable;
// Class implementation goes here
}

Real implementation

The PHP interpreter copies and pastes traits into class definitions at compile time, and it does not protect against incompatibilities introduced by this action. If your PHP trait assumes a class property or method exists (that is not defined in the trait itself), be sure those properties and methods exist in the appropriate classes.

1
2
3
4
5
6
7
8
9
10
<?php
$geocoderAdapter = new \Geocoder\HttpAdapter\CurlHttpAdapter();
$geocoderProvider = new \Geocoder\Provider\GoogleMapsProvider($geocoderAdapter);
$geocoder = new \Geocoder\Geocoder($geocoderProvider);
$store = new RetailStore();
$store->setAddress('420 9th Avenue, New York, NY 10001 USA');
$store->setGeocoder($geocoder);
$latitude = $store->getLatitude();
$longitude = $store->getLongitude();
echo $latitude, ':', $longitude;

Namespace

Delare

1
2
3
<?php
namespace Animal;
namespace Animal\Dog;

Import and Alias

Normal Import

1
2
3
<?php
use Animal\Dog;
use Animal\Dog as DD;

function and constant

1
2
3
4
5
6
7
// PHP 5.6+ only: Import a function
use func Namespace\functionName;
functionName();

// PHP 5.6+ only: Import a constant
use constant Namespace\CONST_NAME;
echo CONST_NAME;

Helpful tips

Multiple imports

1
2
3
4
5
6
7
8
9
10
//Don't do this
<?php
use Symfony\Component\HttpFoundation\Request,
Symfony\Component\HttpFoundation\Response,
Symfony\Component\HttpFoundation\Cookie;

//It should be
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;

Multiple namespace in one file

1
2
3
4
5
6
7
<?php
namespace Foo {
// Declare classes, interfaces, functions, and constants here
}
namespace Bar {
// Declare classes, interfaces, functions, and constants here
}

Global namespace

1
2
3
4
5
6
7
8
9
<?php
namespace My\App;
class Foo
{
public function doSomething()
{
throw new \Exception();
}
}

Why AWS Elastic Beanstalk?

A single server instance is good for development but it would be bad for production environment because it’s unable to scale horizontally. Also it’s a waste of money to let a giant instance run 7x24 while we just have a few rushing hours in a day. Therefore, we need to be able to scale horizontally base on the network traffic, server cpu utilization percentage etc.

How to use Elastic Beanstalk?

I will create the Elastic Beanstalk environment by a cloudformation template and make specific server instance change in project artifact’s .ebextension config files.

Cloudformation Template

Let’s begin with a snippet.

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
AWSTemplateFormatVersion: '2010-09-09'
Resources:
sampleApplication:
Type: AWS::ElasticBeanstalk::Application
Properties:
###################################
# Specify the Application name here
###################################
ApplicationName: Application Name(XXXXX)
Description: AWS Elastic Beanstalk Sample Application
sampleApplicationVersion:
Type: AWS::ElasticBeanstalk::ApplicationVersion
Properties:
ApplicationName:
Ref: sampleApplication
Description: AWS ElasticBeanstalk Sample Application Version
SourceBundle:
###################################
# Specify the artifact's location
###################################
S3Bucket: !Sub "elasticbeanstalk-samples-${AWS::Region}"
S3Key: php-newsample-app.zip
sampleConfigurationTemplate:
Type: AWS::ElasticBeanstalk::ConfigurationTemplate
Properties:
ApplicationName:
Ref: sampleApplication
Description: AWS ElasticBeanstalk Sample Configuration Template
###################################
# Specify the solution stack name
# The list of all solution stack can be found from https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/concepts.platforms.html
###################################
SolutionStackName: 64bit Amazon Linux 2018.03 v2.8.1 running PHP 7.0
OptionSettings:
###################################
# Specify the option settings of environment and platform
# - Environment options: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html
# - Platform specific options: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-specific.html
###################################
- Namespace: aws:autoscaling:asg
OptionName: MinSize
Value: '2'
- Namespace: aws:autoscaling:asg
OptionName: MaxSize
Value: '6'
- Namespace: aws:elasticbeanstalk:environment
OptionName: EnvironmentType
Value: LoadBalanced
sampleEnvironment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
ApplicationName:
Ref: sampleApplication
Description: AWS ElasticBeanstalk Sample Environment
TemplateName:
Ref: sampleConfigurationTemplate
VersionLabel:
Ref: sampleApplicationVersion

NOTE: If we need to have immutable auto scaling, aws:elasticbeanstalk:healthreporting:system -> System Type should be set as “enhanced”.

.ebextensions

How about the software and the script deployment in EC2 instance? How can we refer and use the environment variables in ebextensions? Here is an example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## Install loggly
packages:
yum:
rsyslog-gnutls: []

files:
"/tmp/configure-apache.sh" :
mode: "000755"
owner: root
group: root
source: https://www.loggly.com/install/configure-apache.sh

"/opt/elasticbeanstalk/hooks/appdeploy/post/90_reindex_elastic_cache.sh" :
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
/tmp/configure-apache.sh -a `{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "LogglySubDomain"}}` -u `{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "LogglyUserName"}}` -p `{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "LogglyPassword"}}`
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
packages:
yum:
newrelic-php5: []
rpm:
newrelic: http://yum.newrelic.com/pub/newrelic/el5/x86_64/newrelic-repo-5-3.noarch.rpm

files:
"/tmp/update_nr_application_name.sh" :
mode: "000744"
owner: root
group: root
content: |
#!/bin/bash
sed -i 's/^\;\?newrelic\.appname\s*=.*/newrelic.appname = "`{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "NewRelicAppName"}}`"/' /etc/php.d/newrelic.ini
sed -i 's/^\;\?newrelic\.appname\s*=.*/newrelic.appname = "`{"Fn::GetOptionSetting": {"Namespace": "aws:elasticbeanstalk:application:environment", "OptionName": "NewRelicAppName"}}`"/' /etc/php-7.0.d/newrelic.ini


commands:
01_configure_new_relic:
command: newrelic-install install
env:
NR_INSTALL_SILENT: true
NR_INSTALL_KEY:
"Fn::GetOptionSetting":
Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: NewRelicLicenseKey
02_set_application_name:
command: sudo bash /tmp/update_nr_application_name.sh
03_stop_daemon:
command: sudo /etc/init.d/newrelic-daemon stop

Note:

  • If we want to contain characters like ‘|’ in commands, the command should be placed inside a single quote.

Update npm

npm i -g npm@VERSION

1
npm i -g npm@5.5.1

Create package.json

1
2
3
4
5
# Create package.json with interactive command prompts
npm init

# Create package.json without any command prompts
npm init --yes

List existing packages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# List dependency tree
npm list

# List dependency tree with depth=0
npm list --depth=0

# View package detail information
npm view mongoose

# View package dependencies
npm view mongoose dependencies

# View package all versions
npm view mongoose versions

Versioning

Semantic format: Major.Minor.Patch

  • Caret Version
    ^4.13.6=4.x

  • Tilde Version
    ~1.8.3=1.8.x

Update local package

1
2
3
4
5
6
7
8
# Check outdated packages
npm outdated

# Check global outdated packages
npm -g outdated

# Update packages
npm update

Use npm-check-updates to update packages

1
2
3
4
5
6
7
8
9
10
11
12
# 1. Install npm-check-updates globally
npm i -g npm-check-updates
# 2. Check which packages can be updated
npm-check-updates
# Or
ncu

# 3. Update package.json
ncu -u

# 4. Install dependencies
npm i

Install dev dependencies

1
npm i jshint --save-dev

Uninstall package

1
npm un mongoose

Publish own packages to npm

1
2
3
4
5
mkdir lion-lib
cd lion-lib
npm init --yes
npm login
npm publish

Why do we need it?

Generic allows developers create classes or methods work with any data type.

Let’s take a look at the following example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//List.cs
using System;

namespace Generic
{
public class List
{
public void Add(int value)
{
throw new NotImplementedException();
}

public int this[int index]
{
get
{
throw new NotImplementedException();
}
}
}
}

Read More

If we don’t have enough memory in a Linux machine, we can enable the swap space to increase the “memory”.

Check the free memory and free space

1
2
3
4
5
6
7
8
#check if any swap space enable
sudo swapon --show

#verify there is no active swap
free -h

#check free space of the hard drive
df -h

Create the swap file and enable swap space

1
2
3
4
5
6
7
8
9
10
11
#create the swap file
sudo dd if=/dev/zero of=/swapfile count=4096 bs=1MiB

#make the file only accessible by root
sudo chmod 600 /swapfile

#mark the file as swap space
sudo mkswap /swapfile

#enable the swap file
sudo swapon /swapfile

Make the swap file permanent

1
2
3
4
5
#backup the /etc/fstab file in case anything goes wrong
sudo cp /etc/fstab /etc/fstab.bak

#add the swap file information to the end
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

That’s all for enabling the swap file on Linux. For more information, we can find more on https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-16-04