Salesforce Certified Platform Developer I

After more than 5 years of fiddling around with Apex classes and Visualforce components and focusing on general coding principles, I thought it might be good to learn some of broader Salesforce features that could be easily overlooked by developers. So I took this exam over the weekend: Salesforce Certified Platform Developer I. It was a happy result: PASS. It did not tell me what score I achieved though. Here I just want to list some “new features/points” I discovered during the prep time the week before the exam. Some of these features have existed for years. It is just that I never paid attention to them.

  • Schema Builder. I cannot remember how many times I opened different browser tabs for different SObject definition pages to find relevant fields’ API names, types, picklist values and lookup relationship to other objects. This Schema builder, jut at Setup | App Setup | Schema Builder, is such a powerful tool to do all of those in one place. Moreover, you can add, edit and delete fields and objects by simple drag-and-drops; not to mention there is a “quick find” box to search for things. When customers want the schema of your product’s data model, just mention this to them. Trailhead is at here.
  • Contacts to Multiple Accounts. The Account lookup on Contact usually means the company the contact is most closely associated with. But contacts might work with more than one company. A business owner might own more than one company or a consultant might work on behalf of multiple organizations. Any other accounts associated with the contact represent indirect relationships. The Related Contacts list lets you view current and past relationships. The Salesforce object is AccountContactRole.
  • Quick Deployment. This is a deployment mechanism that rolls out your customizations to production faster by running tests as part of validations and skipping tests in your deployments. This is also useful to prepare a deployment simulation to production before the real deployment happens. Both change set and Ant target for meta data migration support quick deployments. Trailhead is at here.
  • Change Set. I have known the concept for quite a while but have not used it until a recent customer deployment management chance. Have to say it is a tedious work – clicking buttons hundreds of times trying to add relevant components to the deployment. It is only agile when used together with quick deployment and when it deploys relatively small amount of work. It tracks all deployment histories.
  • Enforce CRUD and FLS. I had known that when rendering VisualForce pages, the platform will automatically enforce CRUD and FLS when the developer references SObjects and SObject fields directly in the VisualForce page. However, I have always forgotten to enforce the CRUD and field level security when Visualforce pages are referencing simple string properties that indirectly relate to some SObject fields. Expressions like these should be used more often in this case:
  • Process Builder. A few of the favorite questions the exam asked were about problem solving options. i.e. if we should use Salesforce declarative process automation features or Apex/Trigger code. Apart from formula fields and workflow rules, Salesforce have this strong declarative process automation feature – process builder. It can easily build a wizard by using this tool.
  • Triggers and order of execution. The developer guide is at here. The exam had more than a couple of questions in this area. It is important to remember when before trigger and after trigger get executed. And when there is any workflow rule being involved that can potentially update the same record so to recursively fire the triggers, this is the guide to understand the detail steps of the process.
  • Test Suite. If multiple test classes are selected in the “New Run” from the developer console, they are running concurrently so can sometimes hit the “Unable to unlock the row” error. The correct way is to create a suite of many tests and run the suite. The test classes in the suite are executed one by one sequentially. The suite is also useful in preparing the regression testing.
  • Lightning Components. It is nice to follow the trailhead to get some hands-on experience when learning Lighting. Even though it is still at an early stage and seems to be slow at the preview start-up, it is the modern way of coding the application – single page and JavaScript MVC.
  • Got to learn more standard Salesforce objects such as Opportunity and Lead. Surprisingly, Account to Opportunity is in a master-detail relationship but Account field on Opportunity is not mandatory. There were 3 questions in the exam in relation to Salesforce standard objects and their relationships.

Object alias in SOQL

The object alias used in SOQL can reduce the number of characters in query string and improve the readability. Suppose there are these objects with parent-child relationship:

  • Parent: ObjectA
  • Child: ObjectB
  • Grand Child: ObjectC

And all of these objects have three fields: Field1, Field2 and Field3. A normal SOQL statement that join these objects from the lowest level of the object graph looks like:

List<ObjectC__c> cList = [
        from ObjectC__c

The version with object alias looks like this:

List<ObjectC__c> cList = [
        from ObjectC__c objC, objC.ObjectB__r objB, objB.ObjectA__r objA

Notice that all involved objects are specified in the “from” clause. This is really a sort of DRYness which makes the SOQL less verbose. It is particularly useful in the case that the number of characters is limited such as SOQL queries being used in HTTP GET URLs as part of the REST web service calls.

Count large number of records (more than 50,000)

In a Salesforce org that has more than 50,000 records of an object, the following simple count() query will still hit the Salesforce governor limit: “System.LimitException: Too many query rows: 50001”

System.debug('total: ' + [select count() from Contact]);

Even though it seems that the count function does not need to traverse the whole Contact table, it still does, for specific reasons like checking sharing settings on records so that different users may get different number of records. So is there a way of retrieving the total number of records that are more than 50,000. There are a few, each of which has their cons and pros. Surprisingly, for any of these methods, such simple task would need more coding than we expected.

Method 1: Use Visualforce page with readOnly attribute set to true

Controller class:

public class StatsController {
    public Integer numberOfContacts {
        get {
            if (numberOfContacts == null) {
                numberOfContacts = [select count() from Contact];
            return numberOfContacts;
        private set;

Visualforce page:

<apex:page controller="StatsController" readOnly="true">
    <p>Number of Contacts: {!numberOfContacts}</p>

Then you will be able to see the result when you access this page in the browser. Note that the readOnly attribute has to be set to “true”, otherwise you will still get a Visualforce error complaining “Too many query rows: 50001 “. However, you won’t be able to create a simple controller and a page in Production orgs.

Method 2: Batchable class

public class ContactBatchable implements Database.Batchable<sObject>, Database.Stateful {
    Integer total = 0;

    public Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator('select Id from Contact');

    public void execute(
            Database.BatchableContext BC,
            List<sObject> scope){
        total += scope.size();

    public void finish(Database.BatchableContext BC){
        System.debug('total: ' + total);

And then execute the following statement in developer console.

Database.executeBatch(new ContactBatchable(), 2000);

This is going to be an asynchronous apex job so it can take time. When it is finished, you will be able to see a log with category “Batch Apex” at the top. The debug statement prints info into that log. You will also see a few logs with category “SerialBatchApexRangeChunkHandler” which is the log for each batch.

In the ContactBatchable the start method has to use the query locator, so that it can retrieve the records up to 50 million. (after 50 million? Ask Salesforce support). If you use an iterable in the batch class, the governor limit for the total number of records retrieved by SOQL queries (50,000 for the moment) is still enforced.

If the start method of the batch class returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to 2,000 records.

Still, this method cannot be applied to production orgs as you won’t be able to create an Apex class in Production org.

Method 3: Make a http request using the REST API

By far, this seems to be the simplest way of achieving this and it can be used in production orgs. In developer console, run the following statements:

HttpRequest req = new HttpRequest();

string autho = 'Bearer '+ userInfo.getsessionId();
req.setHeader('Authorization', autho);

Http http = new Http();
HTTPResponse res = http.send(req);
string response = res.getBody();
string total = response.substring(response.indexOf('totalSize":') + 11, response.indexOf(','));
system.debug('Total: '+ total);

You will need to add a remote site setting with URL set to your production org’s URL (say in the setup.

Invoke Contact triggers logic on Person Accounts

To learn about Person Accounts, the features and their intended use, see training guide from Salesforce Implementing Person Accounts. Cons and Pros of enabling Person Accounts are discussed on many blogs. Business people embrace it but developers tend to stay away. However, it is essential to take Person Account into consideration if the managed package should be “Person Account compatible”, i.e. your managed package should work as expected in a Person Account enabled org. A particular nasty issue is: Inserts, updates, and deletes on Person Accounts fire Account triggers, not Contact triggers. So when your managed package is deployed to a Person Account enabled org, the logic on Contact will not be fired if there is any change on Person Account record. For example, a validation rule is implemented on Contact trigger to prevent the user from updating Birth Date to a future date. This should be applied to Person Account as well if Birth Date is used on Person Account. However, this logic won’t be executed on an update of a Person Account record.

The first intuitive solution coming to the mind is to invoke the Contact triggers from within Account triggers. If there’s a way to do it, I haven’t found it. The next solution would be re-implement the same logic of Contact triggers in Account triggers. But this is an obvious Don’t Repeat Yourself (DRY) principle violation. It is simply duplicate code with minor changes. This post illustrates a way of executing the same logic of Contact triggers on Person Accounts, rather than trying to invoke Contact triggers on Person Accounts. It uses a wrapper to represent either Contact or Account and refactor the Contact triggers logic out to a common component that Account triggers can share. The common component deals with the wrapper objects and does not care if the objects are Contacts or Person Accounts.

Firstly, a ContactWrapper is defined to represent either a Contact object or a Person Account object. The aim is to provide a common interface for retrieving and updating fields values on either Contact or Person Account object. The calling class can be a utility class that implements common logic for Person and does not care if the Person is represented by Contact or Person Account. The common logic usually needs to be invoked by both Contact and Account triggers.

// ContactWrapper.cls
public class ContactWrapper {
    private static final SObjectField CONTACT_BIRTH_DATE = Contact.BirthDate;

    private SObject sob;
    private Boolean isPersonAccount;
    public Date birthDate {
        get {
            return (Date) sob.get(getField(CONTACT_BIRTH_DATE));
        set {
            sob.put(getField(CONTACT_BIRTH_DATE), value);

    public ContactWrapper(Contact c) {
        if (c == null) {
            throw new ContactWrapperException('Contact cannot be null.');
        this.sob = (SObject) c;
        isPersonAccount = false;
    public ContactWrapper(Account a) {
        if (!PersonAccounts.isEnabledForOrg()) {
            throw new ContactWrapperException('Person Account is not supported for the current org.');
        if (a == null) {
            throw new ContactWrapperException('Account cannot be null.');
        if (a.get('IsPersonAccount') == false) {
            throw new ContactWrapperException('Only Person Account can be used by ContactWrapper.');
        this.sob = (SObject) a;
        isPersonAccount = true;
    public SObject getSob() {
        return sob;
    public Object get(String contactFieldName) {
        String fieldName = contactFieldName;
        if (isPersonAccount) {
            fieldName = PersonAccounts.getFieldName(contactFieldName);
            if (fieldName == null) {
                throw new ContactWrapperException('Cannot find corresponding Account field for Contact field ' + contactFieldName);
        return sob.get(fieldName);
    // Add error message on the SObject. Equivalent to sObject.addError method
    public void addError(String error) {
    // Return the name of the field rather than SObjectField because it will hit a strange error like the following:
    // "Account.BirthDate does not belong to SObject type Account"
    private String getField(SObjectField contactField) {
        if (isPersonAccount) {
            SObjectField accountField = PersonAccounts.getField(contactField);
            if (accountField == null) {
                throw new ContactWrapperException('Cannot find corresponding Account field for Contact field ' + String.valueOf(contactField));
            return String.valueOf(accountField);
        } else {
            return String.valueOf(contactField);
    public class ContactWrapperException extends Exception {}

The above sample code only presents field “birthDate”. In practice, there should be much more depending on the fields references in the Contact triggers. As not every field on Contact has a corresponding field on Person Account, e.g. AccountId, ReportsTo, getters and setters for these fields cannot be defined. Logic related to these fields should be exclusively placed in Contact trigger. Another referenced class in the above code is PersonAccounts, a utility class that deals with Person Account general issues.

// PersonAccounts.cls
public class PersonAccounts {
    public class PersonAccountsException extends Exception {
    private static final Map<String, SObjectField> ACCOUNT_FIELDS_MAP = Schema.SObjectType.Account.fields.getMap();
    private static final Set<String> ACCOUNT_FIELDS_NAMES = ACCOUNT_FIELDS_MAP.keyset();
    public static Boolean isEnabledForOrg() {
        return ACCOUNT_FIELDS_NAMES.contains('personcontactid');
    // Map from Contact field to Account field
    public static SObjectField getField(SObjectField contactField) {
        String fieldName = String.valueOf(contactField);
        // The above fieldName can contain namespace prefix. Trim the prefix before comparing.
        fieldName = StringUtil.removePrefix(fieldName);
        fieldName = fieldName.toLowerCase();
        // Get the __pc field first if this matches as both Account and Contact can have the same custom field like PaymentMethod__c
        String pcFieldName = fieldName.replace('__c', '__pc');
        if (ACCOUNT_FIELDS_NAMES.contains(pcFieldName)) {
            return ACCOUNT_FIELDS_MAP.get(pcFieldName);
        String personFieldName = 'person' + fieldName;
        if(ACCOUNT_FIELDS_NAMES.contains(personFieldName)) {
            return ACCOUNT_FIELDS_MAP.get(personFieldName);
        if (ACCOUNT_FIELDS_NAMES.contains(fieldName)) {
            return ACCOUNT_FIELDS_MAP.get(fieldName);
        return null;
    // Map from Contact field name to Account field name
    public static String getFieldName(String contactFieldName) {
        String fieldName = StringUtil.removePrefix(contactFieldName);
        fieldName = fieldName.toLowerCase();
        String pcFieldName = fieldName.replace('__c', '__pc');
        if (ACCOUNT_FIELDS_NAMES.contains(pcFieldName)) {
            return String.valueOf(ACCOUNT_FIELDS_MAP.get(pcFieldName));
        String personFieldName = 'person' + fieldName;
        if(ACCOUNT_FIELDS_NAMES.contains(personFieldName)) {
            return String.valueOf(ACCOUNT_FIELDS_MAP.get(personFieldName));
        if (ACCOUNT_FIELDS_NAMES.contains(fieldName)) {
            return String.valueOf(ACCOUNT_FIELDS_MAP.get(fieldName));
        return null;

The next step is to move all original Contact triggers logic to a common class interfacing with ContactWrapper. This class contains static methods which represent the common logic that should be invoked by both Contact triggers and Person Account triggers (specifically logic in Account triggers that only care about a list of Person Account records). Any logic applied to both Contact and Person Account should be placed in this class. The class should use ContactWrapper objects as representatives of Contacts or Person Accounts.

// ContactAndPersonAccountTriggerLogic.cls
public class ContactAndPersonAccountTriggerLogic {
    public static void runBefore(
            Boolean isInsert,
            Boolean isUpdate,
            Boolean isDelete,
            List<ContactWrapper> oldList,
            Map<Id, ContactWrapper> oldMap,
            List<ContactWrapper> newList,
            Map<Id, ContactWrapper> newMap) {
        // "before trigger" logic common to Contacts and Person Accounts
    public static void runAfter(
            Boolean isInsert,
            Boolean isUpdate,
            Boolean isDelete,
            List<ContactWrapper> oldList,
            Map<Id, ContactWrapper> oldMap,
            List<ContactWrapper> newList,
            Map<Id, ContactWrapper> newMap) {
        // "after trigger" logic common to Contacts and Person Accounts

Finally, the Contact triggers and Account triggers can simply call the above common class methods so that no logic is duplicated. See the following ContactBeforeTrigger.trigger and AccountBeforeTrigger.trigger as an example. Notice ContactWrappers is a utility class dealing with ContactWrapper objects.

// ContactBeforeTrigger.trigger
trigger ContactBeforeTrigger on Contact (before insert, before update) {
    // Common logic for both Contact and Person Account records.
    Boolean isInsert = Trigger.isInsert;
    Boolean isUpdate = Trigger.isUpdate;
    Boolean isDelete = Trigger.isDelete;
    List<ContactWrapper> oldList = ContactWrappers.asList(Trigger.old);
    Map<Id, ContactWrapper> oldMap = ContactWrappers.asMap(Trigger.oldMap);
    List<ContactWrapper> newList = ContactWrappers.asList(;
    Map<Id, ContactWrapper> newMap = ContactWrappers.asMap(Trigger.newMap);
    ContactAndPersonAccountTriggerLogic.runBefore(isInsert, isUpdate, isDelete, oldList, oldMap, newList, newMap);

    // The following section is for logic that is specific for Contact.
    // Always think if the logic should be applied to Person Account before put the code here.
// AccountBeforeTrigger.trigger
trigger AccountBeforeTrigger on Account (before insert, before update) {
    // Logic for Person Account
    if (PersonAccounts.isEnabledForOrg()) {
        List<ContactWrapper> oldList = ContactWrappers.asList(Trigger.old);
        Map<Id, ContactWrapper> oldMap = ContactWrappers.asMap(Trigger.oldMap);
        List<ContactWrapper> newList = ContactWrappers.asList(;
        Map<Id, ContactWrapper> newMap = ContactWrappers.asMap(Trigger.newMap);
        if ((oldList != null && oldList.size() > 0) ||
                (newList != null && newList.size() > 0)) {
            ContactAndPersonAccountTriggerLogic.runBefore(Trigger.isInsert, Trigger.isUpdate, Trigger.isDelete, oldList, oldMap, newList, newMap);

    // The following section is for logic applied to normal Account (Business Account)

Custom sidebar: Pop up a modal dialog with content from static resource

Salesforce org’s sidebar can be customized by HTML so is often used for placing small components like a table or some hyperlinks to external web pages. Static resources can be used to host any type of files and easily referenced by Visualforce pages and apex classes. So this brings the idea of writing web widgets in pure HTML, CSS and JavaScript and popping them up from the org’s sidebar links. Here is an example and the HowTo:

We developed a utility tool in HTML5, CSS and JavaScript. This tool does not depend on any Salesforce technologies or resources. But we want to incorporate this tool in our Salesforce managed package so that any orgs installing our package can choose to use the tool. Since it is a utility tool, a good place to host it is sidebar component. Let’s call this tool AbcTool.

The AbcTool only has three source files: abc-tool.html, abc-tool.js, abc-tool.css. These files are  packaged in a zip file which is then uploaded as a Salesforce static resource called WebWidgets. The following code shows how to display the AbcTool in a dialog. The code is in the home page component’s custom HTML area. It uses JQueryUI to create a dialog and binds it to the onClick event of “Abc Tool” link.

<link rel="stylesheet" type="text/css"
<script type="text/javascript"
			<td><a href="javascript:void(0)" id="abcTool">ABC
<script type="text/javascript">
$j = jQuery.noConflict();
$j(document).ready(function() {
	var iframe_url = '/apex/cve__AbcTool';
	$j('#abcTool').click(function() {
		var j$modalDialog = $j('<div></div>')
			.html('<iframe id="iframeContentId" src="' + iframe_url + '" frameborder="0" height="100%" width="100%" marginheight="0" marginwidth="0" scrolling="no"/>')
				autoOpen: false,
				title: 'ABC Tool',
				resizable: false,
				width: 550,
				height: 450,
				autoResize: true,
				modal: true,
				draggable: false});

The actual content of the dialog is in Visualforce page as you can see it is referenced by “/apex/cve__AbcTool”. This is the Visualforce page that bridges the sidebar component and static resource which is a zip file. It just redirects the request to the HTML page in the static resource. The following is the AbcTool page:

<apex:page showHeader="false"
    action="{!URLFOR($Resource.WebWidgets, '/abc-tool.html')}" />

Gotcha: convertTimezone() must be used in SOQL Date functions dealing with Datetime

SOQL Date functions are pretty useful for grouping or filtering data by date fields. With a proper Date function used in the SOQL, the code can potentially limit the query result records a lot. e.g. Query all Tasks that are created today:

List<Task> = [
        select Id, WhatId, Subject, CreatedDate
        from Task
        where DAY_ONLY(CreatedDate) =];

The above code looks neat enough although the function DAY_ONLY is not that obviously named. The documentation states: “Returns a date representing the day portion of a dateTime field.” so it should be safe enough. I used it in a few places and it worked very well. However recently I got a failing unit test while I was creating a managed package. The unit test was testing the logic that uses the above code. The test only fails when it runs after 5pm GMT-7 and before 12am GMT-7. The local time zone of the org is GMT-7.

I started debugging the issue in that precious 7-hour window period. I created a sample Task after 5pm GMT-7 and ran the above code immediately and it returned no records! I changed “” to “ + 1” in the where clause, re-ran the code and it returned the Task I just created. What? Is this saying “Get me all Tasks created tomorrow”? Obviously not, the only explanation on this is that the DAY_ONLY() function takes Datetime parameter as GMT time. Any time between 5pm GMT-7 and 12am GMT-7 is already tomorrow’s time GMT.

All of a sudden I realized that all those Date functions like CALENDAR_YEAR, DAY_IN_MONTH etc. could be useless as they are all having the same timezone issue. That cannot be right. I got back to the documentation and found it has this statement:

“SOQL queries in a client application return dateTime field values as Coordinated Universal Time (UTC) values. To convert dateTime field values to your default time zone, see Converting Time Zones in Date Functions.”

On the “Converting Time Zones in Date Functions” web page, it says “You can use convertTimezone() in a date function to convert dateTime fields to the user’s time zone.”. What do you mean by “You CAN…”? In an org with a different timezone other than GMT, you HAVE to use this method to make it working! This is obviously a compromised solution of fixing the original timezone issue in those Date functions.

Generally speaking, the timezone issue is introduced because in Apex, Datetime and Date are not clearly differentiated and Salesforce badly handles these two types. See some other Datetime issues in this blog post: Danger: Date value can be assigned to a Datetime variable.

Anyway, the fix of the above code is:

List<Task> = [
        select Id, WhatId, Subject, CreatedDate
        from Task
        where DAY_ONLY(convertTimezone(CreatedDate)) =];

Apex controller class “without sharing” in practice

A general practice of defining a controller is to prepend “with sharing” key word to “class”. This is to enforce the sharing rules that apply to the current user as otherwise sharing rules aren’t taken into account during code execution. Recently in a project I came across a requirement in which “without sharing” plays a role.

We have a custom object called “Payment” and a Approval Process is defined on this object. The requirement is that system should allow any users other than the user who submitted Payments for approval to bulk approve or reject Payment approval requests. Originally we focused on implementation of bulk approve/reject logic. We created a custom controller and a Visualfore page to did all it needs. It felt so good that we could programmatically approve/reject approval requests in a bulk in Apex code. But we got a bug from the customer saying “It only allows the manager of the users who submitted Payments for approval to approve/reject the Payments”. When other users are approving/rejecting Payments, they get the following error:

Process failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY,  insufficient access rights on cross-reference id: []

The debug log shows that this error is thrown when Approval.process() method is invoked. This actually makes sense as the controller class has “with sharing” declared. Normally if a user submitted an object for approval, only the user that is assigned to this approval request (usually the manger of the submitting user) has the access rights to corresponding Approval Process work items. Other users won’t even be able to see the actions links on the form. Since the approval requests are programmatically processed via a page and a “with sharing” controller, a random person will be able to run the code but does surely not have the access rights to Approval Process work items. This is proved by the Salesforce documentation on Using the “with sharing” or “without sharing” Keywords. Related statement:

Enforcing the current user’s sharing rules can impact:

  • SOQL and SOSL queries. A query may return fewer rows than it would operating in system context.
  • DML operations. An operation may fail because the current user doesn’t have the correct permissions. For example, if the user specifies a foreign key value that exists in the organization, but which the current user does not have access to.

So I got back to the controller, changed the “with” to “without” and it fixed the problem. However, all related Profile based security rules should be programmatically implemented. The profile setting on access to the corresponding Visualforce page can be used to do a basic protection on access to this “bulk approve/reject” functionality.